【问题标题】:In Python can isinstance() be used to detect a class method?在 Python 中可以使用 instance() 来检测类方法吗?
【发布时间】:2021-12-10 20:54:00
【问题描述】:

如何判断一个对象是否为类方法?使用 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

【问题讨论】:

  • classmethod 没有命名方法对象的类型。为此,您可能想使用Callable
  • 因为classmethod 实现了描述符协议,所以您必须检查Foo.__dict__['bar'],因为Foo.bar 会生成method 的实例。
  • 虽然classmethod 是一种类型,并且应用@classmethod 装饰器确实会从一个函数中创建一个classmethod 实例,但稍后在类中查找该名称仍然会给您一个普通的方法实例。这是由于描述符协议的工作方式。 (虽然这实际上只是表达@chepner 所说的另一种方式。)
  • 仅供参考。 Callable 为每种类型的方法和函数返回 True。

标签: python class-method isinstance


【解决方案1】:

如果您只是想将类方法与常规方法和静态方法区分开来,那么您可以使用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 是一个类,something 是常规方法、类方法或静态方法。

【讨论】:

  • 从技术上讲,isinstance 正在测试每个类属性的 __get__ 方法返回什么,而不是类属性本身。
  • 如果输入函数,这些方法也会正确返回 False。奖励积分。
【解决方案2】:

如您所知,Python 使用对类本身的引用填充classmethods 的第一个参数,无论您是从类还是从类的实例调用该方法都没有关系。方法对象是绑定了对象的函数。

可以通过.__self__ 属性检索该对象。所以你可以简单地检查.__self__ 属性是否是一个类。如果是类,它的类是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 函数专门检查对象是否为绑定方法。您也可以在检查第一个参数的类型之前使用它。

注意:上述解决方案有一个警告,我会在最后提及。

解决方案二:

您的isinstance 解决方案不起作用,因为classmethod 是一个描述符。如果你想获取实际的 classmethod 实例,你应该检查 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 对象,其绑定对象是不同的类,例如此处的 int,则此解决方案将不起作用。因为请记住,我们刚刚检查了第一个参数是否为 type 类型:

from types import MethodType

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

    @classmethod
    def fn1(cls):
        pass

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

【讨论】:

  • 我会选择解决方案 #2;我认为,解决方案 #1 取决于 classmethod 是唯一具有 type-valued __self__ 属性的类型,而不是唯一可以具有 @ 属性的类型987654339@-valued __self__ 属性。
  • @chepner 你说得对,我更新了答案并举了一个例子。
  • is_classmethod(cls, m) 如果您不小心为其提供了一个函数,则会崩溃,因此替代方案更安全。
  • @JohnMatecsa Foo.fn2 是一个函数,它返回 False。你是怎么测试的?
猜你喜欢
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 2018-11-08
  • 1970-01-01
  • 2011-02-11
相关资源
最近更新 更多