Python decorator himself confused

I'm new to Python decorators (wow, great feature!), And it’s not easy for me to get the following to work, because the self argument takes the form of confusion.

 #this is the decorator class cacher(object): def __init__(self, f): self.f = f self.cache = {} def __call__(self, *args): fname = self.f.__name__ if (fname not in self.cache): self.cache[fname] = self.f(self,*args) else: print "using cache" return self.cache[fname] class Session(p.Session): def __init__(self, user, passw): self.pl = p.Session(user, passw) @cacher def get_something(self): print "get_something called with self = %s "% self return self.pl.get_something() s = Session(u,p) s.get_something() 

When I run this, I get:

 get_something called with self = <__main__.cacher object at 0x020870F0> Traceback: ... AttributeError: 'cacher' object has no attribute 'pl' 

for the line where I am self.cache[fname] = self.f(self,*args)

Problem . Obviously, the problem is that self is a cacher object instead of a session instance that does not really have the pl attribute. However, I cannot find how to solve this.

The solutions that I examined but cannot use - I thought that the decorator class would return a function instead of a value (as in section 2.1 of this article ), so that self is evaluated in the correct context, but this is not possible, since my decorator is implemented as a class and uses the built-in __call__ . Then I decided not to use the class for my decorator, so I do not need the __call__ method, but I can not do this because I need to maintain state between calls to the decorator (i.e., to track what is in self.cache ) .

Question So, besides using the global cache dictionary variable (which I have not tried, but I guess it will work), is there any other way to make this decorator work?

Edit: this SO question is similar to Decorating python class methods, how do I pass an instance to a decorator?

+17
python decorator python-decorators
Mar 29 '11 at 8:45
source share
3 answers

Use the protocol descriptor as follows:

 import functools class cacher(object): def __init__(self, f): self.f = f self.cache = {} def __call__(self, *args): fname = self.f.__name__ if (fname not in self.cache): self.cache[fname] = self.f(self,*args) else: print "using cache" return self.cache[fname] def __get__(self, instance, instancetype): """Implement the descriptor protocol to make decorating instance method possible. """ # Return a partial function with the first argument is the instance # of the class decorated. return functools.partial(self.__call__, instance) 

Edit:

How it works?

Using the descriptor protocol in the decorator will allow us to access the method decorated with the correct instance as self, maybe some code may help better:

Now that we do this:

 class Session(p.Session): ... @cacher def get_something(self): print "get_something called with self = %s "% self return self.pl.get_something() 

equivalent to:

 class Session(p.Session): ... def get_something(self): print "get_something called with self = %s "% self return self.pl.get_something() get_something = cacher(get_something) 

So now get_something is an instance of cacher. therefore, when we call the get_something method, it will be translated to this (due to the descriptor protocol):

 session = Session() session.get_something # <==> session.get_something.__get__(get_something, session, <type ..>) # NB: get_something is an instance of cacher class. 

and that's why:

 session.get_something.__get__(get_something, session, <type ..>) # return get_something.__call__(session, ...) # the partial function. 

So

 session.get_something(*args) # <==> get_something.__call__(session, *args) 

Hope this explains how it works :)

+30
Mar 29 2018-11-11T00:
source share

Closing is often the best way, since you do not need to guess with the handle protocol. Saving a mutable state through calls is even easier than with a class, because you simply insert a mutable object into the content area (links to immutable objects can be processed either using the nonlocal keyword or by accumulating them in a mutable object, for example, a single list).

 #this is the decorator from functools import wraps def cacher(f): # No point using a dict, since we only ever cache one value # If you meant to create cache entries for different arguments # check the memoise decorator linked in other answers print("cacher called") cache = [] @wraps(f) def wrapped(*args, **kwds): print ("wrapped called") if not cache: print("calculating and caching result") cache.append(f(*args, **kwds)) return cache[0] return wrapped class C: @cacher def get_something(self): print "get_something called with self = %s "% self C().get_something() C().get_something() 

If you are not completely familiar with how closures work, adding additional print statements (as I have already mentioned above) may be illustrative. You will see that cacher is only called as a function is defined, but wrapped is called every time the method is called.

This emphasizes that you need to be careful with memoisation methods and instance methods, although - if you are not careful to account for changes in the value of self , you will end up sharing cached answers between instances, which may not be what you want.

+3
Mar 29 '11 at 13:00
source share

First, you explicitly pass the cacher object as the first argument in the following line:

 self.cache[fname] = self.f(self,*args) 

Python automatically adds self to the argument list only for methods. It converts functions (but not other calls like your cacher ! Object), defined in the class namespace for methods. To get this behavior, I see two ways:

  • Modify the decorator to return the function using closure.
  • Implement the descriptor protocol to pass the self argument yourself, as is done in the memoize decorator recipe .
+1
Mar 29 '11 at 9:19
source share



All Articles