简体   繁体   中英

In Python can instance() be used to detect a class method?

How to determine if an object is a class method? Isn't it best practice to use instance(), 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

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 .

One way of doing it:

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 aismethod 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.

Solution number 2:

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))

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(self): 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 eg 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM