1

I'd like to write a decorator that does somewhat different things when it gets a function or a method. for example, I'd like to write a cache decorator but I don't want to have self as part of the key if it's a method.

def cached(f):
    def _internal(*args, **kwargs):
        if ismethod(f):
            key = create_key(*args[1:], **kwargs) # ignore self from args
        else: # this is a regular function
            key = create_key(*args, **kwargs)
        return actual_cache_mechanism(key, f, *args, **kwargs)
    return _internal

class A:
    @cached
    def b(self, something):
       ...

@cached
def c(something):
    ...

the problem is that when @cached is called, it cannot distinguish between methods and functions as both are of type function. can that even be done? As I'm thinking of it I feel that actually methods have no idea about the context in which they are being defined in...

Thanks!

Nadav
  • 13
  • 1
  • 2
  • 1
    Does it need to know the difference between a function and method? Decorators shouldn't need to ever know the difference. Just pass `*args` no matter what. A method will pass a class instance (`self`) as an argument. A function will not. Decorators can just pass `*args` in either case. – Error - Syntactical Remorse Jun 19 '19 at 14:51
  • You can make your decorator return a callable descriptor (like a function is) and react to the lookup that produces bound methods. Is that approach acceptable? – Davis Herring Jun 19 '19 at 15:24
  • 1
    I am just wondering why do you need to ignore `self`. If you can omit `self` from the cache key without breaking anything, that means the result is not depending on it and `self` is not needed at all. Such method should be defined as `staticmethod` or maybe `classmethod` (and problem is solved). – VPfB Jun 19 '19 at 15:30
  • @VPfb that's not entirely true.. for example, `self` might contain transient data (like a socket) but its current state is not essentially different from a future state. so `self` is needed, but does not belong to the cache key. @davis-herring can you elaborate? not sure I understand your approach... – Nadav Jun 20 '19 at 09:18
  • @Nadav. Yes, you are right about the socket (as an example). Actually I posted a question myself about ignoring such argument in `lru_cache`. However here the OP asks for a decorator working completely without the socket (i.e. self) as one of it uses. – VPfB Jun 20 '19 at 14:48
  • Thanks for all the answers and suggestions! I would say python really doesn't want me (or anyone..) to actually do that, but I wanted to know if anyone had a clever idea. As an example, we have in functools `partial` and its counterpart `partialmethod`. so it is known in the core team. @VPfB - thanks for the ideas and the reference! – Nadav Jun 23 '19 at 07:54

2 Answers2

0

This is kind of an ugly hack, but you can use obj.__qualname__ to see if obj was defined in a class, by checking if it has a period

if "." in obj.__qualname__":
   #obj is a member of an object, so it is a method

I'm not sure if it will work nicely for decorators though, since for this to work the method would need to be defined in the class.

Juan Carlos Ramirez
  • 2,054
  • 1
  • 7
  • 22
  • this is somewhat interesting.. you can use __module__ and __name__ to check if the "path" of the object, see if it's a class... – Nadav Jun 20 '19 at 09:37
0

I think it is desirable to avoid such introspecting decorator in the name of good pythonic style.

You can always factor out the function to be cached to accept just the required arguments:

@cached
def func(something):
    return ...

class A:
    def b(self, something):
        self.bvalue = func(something)

For the case mentioned in comments (an object is needed to get the result, but its value does not affect it, e.g. a socket), please refer to these questions: How to ignore a parameter in functools. lru_cache? and Make @lru_cache ignore some of the function arguments

VPfB
  • 14,927
  • 6
  • 41
  • 75