15

TL;DR我如何确定一个函数是使用定义的@classmethod还是具有相同效果的东西?


我的问题

为了实现一个类装饰器,我想检查一个方法是否将类作为它的第一个参数,例如通过

@classmethod
def function(cls, ...):

@staticmethod我找到了一个通过types模块检查的解决方案(isinstance(foo, types.UnboundMethodType)如果Falsefoo静态的,请参见此处),但没有找到有关如何执行此操作的任何信息@classmethod


语境

我正在尝试做的是类似的事情

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

而且我不知道如何实现我isclassmethod在这个例子中所说的

4

5 回答 5

26

如果该对象是一个方法对象,因此具有一个method.__self__属性,并且该属性是您从中获取该属性的类,那么它将将该类作为第一个参数。它已绑定到类。

请注意,此时您已经有一个绑定对象,因此您不需要再次传入该类,除非您首先从method.__func__.

这里举例说明,类Foo有类方法bar和常规方法baz,直接在类上访问时不绑定:

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

调用会自动作为第一个参数Foo.bar()传入。Foo.bar.__self__

如果您需要测试此类方法,请使用inspect.ismethod(),如果返回True测试__self__属性:

import inspect

if inspect.ismethod(cls.method) and cls.method.__self__ is cls:
    # method bound to the class, e.g. a classmethod

这也适用于任何类似的自定义描述符classmethod

如果您需要确定该方法是由对象生成的,则需要直接在类命名空间(或)classmethod中查找属性,并按照方法解析顺序在类层次结构中的每个类中执行此操作:cls.__dict__vars(cls)

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

并使用基类和派生类对上述两种方法进行全面测试,并使用自定义描述符以与 a 相同的方式绑定函数classmethod,但它本身不是 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

isclassmethod()函数正确区分classmethodnotclassmethod描述符。


历史注释:此答案包括对 Python 2 的引用,但由于 Python 2 已达到 EOL,因此不再相关。

于 2013-10-07T15:05:28.513 回答
9

您应该使用inspect.ismmethod。它之所以有效,是因为 classmethod 将函数绑定到类对象。请参阅以下代码:

>>> 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
于 2013-10-07T14:58:54.733 回答
2
class Foo(object):
    @classmethod
    def baaz(cls):
        print "baaz"

isinstance(Foo.__dict__["baaz"], classmethod)
于 2013-10-07T15:28:18.083 回答
2

没有一个答案解决了识别方法是否用类实例中的类方法修饰的问题。下面的代码探索了一个实例的类字典,以区分类方法和其他方法。

class MyClass(object):
    @classmethod
    def class_method(cls):
        pass

    def instance_method(self):
        pass

    @staticmethod
    def static_method(): 
        pass

    def blas(): pass

t = MyClass()
isinstance(t.__class__.__dict__[t.class_method.__name__], classmethod)    # True
isinstance(t.__class__.__dict__[t.static_method.__name__], classmethod)   # False
isinstance(t.__class__.__dict__[t.instance_method.__name__], classmethod) # False
isinstance(t.__class__.__dict__[t.blas.__name__], classmethod)            # False

这适用于 Python 2 和 3。

于 2018-08-01T08:10:53.430 回答
0

这对我有用:

def is_classmethod(method):
    """
    Is method a classmethod?
    """
    return isinstance(getattr(method, '__self__', None), type)

它基本上测试是否method.__self__存在并且是一个类,就像 Martijn 的回答一样,但不需要访问类本身。

于 2017-08-09T17:03:11.823 回答