I had an interesting problem this morning. I had a base class that looked like this:
# base.py
class Base(object):
@classmethod
def exists(cls, **kwargs):
# do some work
pass
And a decorator module that looked like this:
# caching.py
# actual caching decorator
def cached(ttl):
# complicated
def cached_model(ttl=300):
def closure(model_class):
# ...
# eventually:
exists_decorator = cached(ttl=ttl)
model_class.exists = exists_decorator(model_class.exists))
return model_class
return closure
Here's my subclass model:
@cached_model(ttl=300)
class Model(Base):
pass
Thing is, when I actually call Model.exists, I get complaints about the wrong number of arguments! Inspecting the arguments in the decorator shows nothing weird going on - the arguments are exactly what I expect, and they match up with the method signature. How can I add further decorators to a method that's already decorated with classmethod
?
Not all models are cached, but the exists() method is present on every model as a classmethod, so re-ordering the decorators isn't an option: cached_model
can add classmethod to exists(), but then what makes exists() a classmethod on uncached models?
To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.
The static method does not take any specific parameter. Class method can access and modify the class state. Static Method cannot access or modify the class state. The class method takes the class as parameter to know about the state of that class.
The @classmethod decorator is a built-in function decorator which is an expression that gets evaluated after your function is defined. The result of that evaluation shadows your function definition. A class method receives the class as the implicit first argument, just like an instance method receives the instance.
The @classmethod is an alternative of the classmethod() function. It is recommended to use the @classmethod decorator instead of the function because it is just a syntactic sugar.
In Python, when a method is declared, in a function body, it is exactly like a function - once the class is parsed and exists, retrieving the method through the "." operator transforms that function - on the fly - into a method. This transform does add the first parameter to the method (if it is not an staticmethod) -
so:
>>> class A(object):
... def b(self):
... pass
...
>>> A.b is A.b
False
Becasue each retrieving of the "b" attribute of "A" yields a different instance of the "method object b"
>>> A.b
<unbound method A.b>
The original function "b" can be retrieved without any trasnform if one does
>>> A.__dict__["b"]
<function b at 0xe36230>
For a function decorated with @classmethod
just the same happens, and the value "class" is added to the parameter list when it is retrieved from A.
The @classmethod
and @staticmethod
decorators will wrap the underlying function in a different descriptor than the normal instancemethod. A classmethod object - which is what a function becomes when it is wrapped with classmethod
is a descriptor object, which has a '__get__' method which will return a function wrapping the underlying function - and adding the "cls" parameter before all the other ones.
Any further decorator to a @classmethod
has to "know" it is actually dealing with a descriptor object, not a function. -
>>> class A(object):
... @classmethod
... def b(cls):
... print b
...
>>> A.__dict__["b"]
<classmethod object at 0xd97a28>
So, it is a lot easier to let the @classmethod
decorator to be the last one to be applied to the method (the first one on the stack) - so that the other decorators work on a simple function (knowing that the "cls" argument will be inserted as the first one).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With