Reputation: 94
How to determine if an object is a class method? Isn't it best practice to use isinstance()
, and how does one make that work?
class Foo:
class_var = 0
@classmethod
def bar(cls):
cls.class_var += 1
print("class variable value:", cls.class_var)
def wrapper(wrapped: classmethod):
"""
Call the wrapped method.
:param wrapped (classmethod, required)
"""
wrapped()
Foo.bar()
wrapper(Foo.bar)
print("the type is:", type(Foo.bar))
print("instance check success:", isinstance(Foo.bar, classmethod))
Output:
class variable value: 1
class variable value: 2
the type is: <class 'method'>
instance check success: False
Process finished with exit code 0
Upvotes: 3
Views: 1756
Reputation: 51037
If you just want to tell class methods apart from regular methods and static methods, then you can check this with inspect.ismethod(f)
.
class A:
def method(self): pass
@classmethod
def class_method(cls): pass
@staticmethod
def static_method(): pass
In the REPL:
>>> from inspect import ismethod
>>> ismethod(A.method)
False
>>> ismethod(A.class_method)
True
>>> ismethod(A.static_method)
False
If you prefer to do this with isinstance
, then that's possible using typing.types.MethodType
:
>>> from typing import types
>>> isinstance(A.method, types.MethodType)
False
>>> isinstance(A.class_method, types.MethodType)
True
>>> isinstance(A.static_method, types.MethodType)
False
Note that these tests will incorrectly identify e.g. A().method
because really we're just testing for a bound method as opposed to an unbound function. So the above solutions only work assuming that you are checking A.something
where A
is a class and something
is either a regular method, a class method or a static method.
Upvotes: 5
Reputation: 16476
As you know Python fills the first parameter of the classmethod
s with a reference to the class itself and it doesn't matter if you call that method from the class or the instance of the class. A method object is a function which has an object bound to it.
That object can be retrieved by .__self__
attribute. So you can simply check that if the .__self__
attribute is a class or not. If it is a class , it's class is type
.
class Foo:
@classmethod
def fn1(cls):
pass
def fn2(self):
pass
def is_classmethod(m):
first_parameter = getattr(m, '__self__', None)
if not first_parameter:
return False
type_ = type(first_parameter)
return type_ is type
print(is_classmethod(Foo.fn1))
print(is_classmethod(Foo().fn1))
print("-----------------------------------")
print(is_classmethod(Foo.fn2))
print(is_classmethod(Foo().fn2))
output:
True
True
-----------------------------------
False
False
There is a ismethod
function in inspect module that specifically checks that if the object is a bound method. You can use this as well before checking for the type of the first parameter.
NOTE: There is a caveat with the above solution, I'll mention it at the end.
Your isinstance
solution didn't work because classmethod
is a descriptor. If you want to get the actual classmethod instance, you should check the Foo's namespace and get the methods from there.
class Foo:
@classmethod
def fn1(cls):
pass
def fn2(self):
pass
def is_classmethod(cls, m):
return isinstance(cls.__dict__[m.__name__], classmethod)
print(is_classmethod(Foo, Foo.fn1))
print(is_classmethod(Foo, Foo().fn1))
print("-----------------------------------")
print(is_classmethod(Foo, Foo.fn2))
print(is_classmethod(Foo, Foo().fn2))
Solution number 1 caveat: For example if you have a simple MethodType
object whose bound object is a different class like int
here, this solution isn't going to work. Because remember we just checked that if the first parameter is of type type
:
from types import MethodType
class Foo:
def fn2(self):
pass
fn2 = MethodType(fn2, int)
@classmethod
def fn1(cls):
pass
Now only solution number 2 works.
Upvotes: 0