TL;DR How do I find out whether a function was defined using @classmethod
or something with the same effect?
My problem
For implementing a class decorator I would like to check if a method takes the class as its first argument, for example as achieved via
@classmethod
def function(cls, ...):
I found a solution to check for @staticmethod
via the types
module (isinstance(foo, types.UnboundMethodType)
is False
if the foo
is static, see here), but did not find anything on how to do so for @classmethod
Context
What I am trying to do is something along the lines of
def class_decorator(cls):
for member in cls.__dict__:
if (isclassmethod(getattr(cls, member))):
# do something with the method
setattr(cls, member, modified_method)
return cls
and I do not know how to implement what I called isclassmethod
in this example
MethodName() . The class method can also be called using an object of the class. 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.
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 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.
You can use class methods for any methods that are not bound to a specific instance but the class. In practice, you often use class methods for methods that create an instance of the class. When a method creates an instance of the class and returns it, the method is called a factory method.
If the object is a method object, and so has a method.__self__
attribute, and that attribute is the class you got the attribute from, then it'll take the class as the first argument. It has been bound to the class.
Note that you already have a bound object at this point, so you don't need to pass in the class again, unless you first extract the original function from method.__func__
.
Here is an illustration, the class Foo
has a class method bar
and a regular method baz
, which is not bound when you access it directly on the class:
>>> class Foo:
... @classmethod
... def bar(cls):
... pass
... def baz(self):
... pass
...
>>> Foo.baz
<function Foo.baz at 0x1097d1e18>
>>> Foo.bar
<bound method Foo.bar of <class '__main__.Foo'>>
>>> Foo.bar.__self__
<class '__main__.Foo'>
>>> Foo.bar.__self__ is Foo
True
Calling Foo.bar()
automatically passes in Foo.bar.__self__
as the first argument.
If you need to test such methods, use inspect.ismethod()
, and if that returns True
test the __self__
attribute:
import inspect
if inspect.ismethod(cls.method) and cls.method.__self__ is cls:
# method bound to the class, e.g. a classmethod
This should work for any custom descriptors that work like classmethod
does, as well.
If you need to know with certainty that the method was produced by a classmethod
object, you'll need to look up the attributes directly in the class namespace (cls.__dict__
or vars(cls)
), and do so in each class in the class hierarchy in method resolution order:
def isclassmethod(method):
bound_to = getattr(method, '__self__', None)
if not isinstance(bound_to, type):
# must be bound to a class
return False
name = method.__name__
for cls in bound_to.__mro__:
descriptor = vars(cls).get(name)
if descriptor is not None:
return isinstance(descriptor, classmethod)
return False
and a full test of the above two approaches using a base class and a derived class, with a custom descriptor that binds a function the same way a classmethod
would, but is not, itself, a classmethod
:
>>> class notclassmethod:
... def __init__(self, f):
... self.f = f
... def __get__(self, _, typ=None):
... return self.f.__get__(typ, typ)
...
>>> class Base:
... @classmethod
... def base_cm(cls): pass
... @notclassmethod
... def base_ncm(cls): pass
... def base_m(self): pass
...
>>> class Derived(Base):
... @classmethod
... def derived_cm(cls): pass
... @notclassmethod
... def derived_ncm(cls): pass
... def derived_m(self): pass
...
>>> inspect.ismethod(Derived.base_cm) and Derived.base_cm.__self__ is Derived
True
>>> inspect.ismethod(Derived.base_ncm) and Derived.base_ncm.__self__ is Derived
True
>>> inspect.ismethod(Derived.base_m) and Derived.base_m.__self__ is Derived
False
>>> inspect.ismethod(Derived.derived_cm) and Derived.derived_cm.__self__ is Derived
True
>>> inspect.ismethod(Derived.derived_ncm) and Derived.derived_ncm.__self__ is Derived
True
>>> inspect.ismethod(Derived.derived_m) and Derived.derived_m.__self__ is Derived
False
>>> isclassmethod(Derived.base_cm)
True
>>> isclassmethod(Derived.base_ncm)
False
>>> isclassmethod(Derived.base_m)
False
>>> isclassmethod(Derived.derived_cm)
True
>>> isclassmethod(Derived.derived_ncm)
False
>>> isclassmethod(Derived.derived_m)
False
The isclassmethod()
function correctly distinguishes between the classmethod
and notclassmethod
descriptors.
Historical note: this answer included references to Python 2, but with Python 2 having reached EOL were removed as no longer relevant.
You should use inspect.ismethod. It works because classmethod binds the function to the class object. See the following code:
>>> class Foo:
... @classmethod
... def bar():
... pass
... def baz():
... pass
...
>>> Foo.bar
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo.baz
<function Foo.baz at 0x0000000002CCC1E0>
>>> type(Foo.bar)
<class 'method'>
>>> type(Foo.baz)
<class 'function'>
>>> import inspect
>>> inspect.ismethod(Foo.bar)
True
>>> inspect.ismethod(Foo.baz)
False
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