在 Python 中 instance() 可以用来检测 class 方法吗?

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

如何判断一个对象是否是class方法?使用 instance() 不是最佳实践吗?如何实现这一点?

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

输出:

class variable value: 1
class variable value: 2
the type is: <class 'method'>
instance check success: False

Process finished with exit code 0

如您所知,Python 使用对 class 本身的引用填充 classmethod 的第一个参数,如果您从 class 或 class 的实例。方法对象是一个绑定了对象的函数。

可以通过 .__self__ 属性检索该对象。因此,您可以简单地检查 .__self__ 属性是否为 class。如果是 class , 就是 class 就是 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))

输出:

True
True
-----------------------------------
False
False

inspect 模块中有一个 ismethod 函数专门检查对象是否是绑定方法。您也可以在检查第一个参数的类型之前使用它。

注意:上面的解决方案有一个警告,我会在最后提到它。

解决方案编号 2:

您的 isinstance 解决方案无效,因为 classmethod 是一个描述符。如果您想获取实际的 class 方法实例,您应该检查 Foo 的命名空间并从那里获取方法。

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

解决方案 1 警告:例如,如果您有一个简单的 MethodType 对象,其绑定对象与 class 不同,例如 int在这里,这个解决方案是行不通的。因为记得我们刚刚检查了第一个参数是否是 type:

类型
from types import MethodType

class Foo:
    def fn2(self):
        pass
    fn2 = MethodType(fn2, int)

    @classmethod
    def fn1(cls):
        pass

现在只有解决方案 2 有效。

如果您只想将 class 方法与常规方法和静态方法区分开来,那么您可以使用 inspect.ismethod(f).

进行检查
class A:
    def method(self): pass
    @classmethod
    def class_method(cls): pass
    @staticmethod
    def static_method(): pass

在 REPL 中:

>>> from inspect import ismethod
>>> ismethod(A.method)
False
>>> ismethod(A.class_method)
True
>>> ismethod(A.static_method)
False

如果您更喜欢使用 isinstance 执行此操作,则可以使用 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

请注意,这些测试会错误地识别例如A().method 因为实际上我们只是在测试绑定方法而不是未绑定函数。因此,上述解决方案仅在假设您正在检查 A.something 的情况下起作用,其中 A 是 class 并且 something 是常规方法、class 方法或静态方法。