Python 检查函数是否使用@classmethod

Python 检查函数是否使用@classmethod,python,decorator,class-method,python-decorators,Python,Decorator,Class Method,Python Decorators,TL;DR如何确定函数是使用@classmethod定义的还是具有相同效果的 我的问题 为了实现类装饰器,我想检查一个方法是否将类作为其第一个参数,例如通过 @classmethod def function(cls, ...): 我找到了一个通过类型模块检查@staticmethod的解决方案(isinstance(foo,types.UnboundMethodType)为False如果foo是静态的,请参见),但没有找到任何关于如何为@classmethod执行此操作的信息 上下文 我

TL;DR如何确定函数是使用
@classmethod
定义的还是具有相同效果的


我的问题

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

@classmethod
def function(cls, ...):
我找到了一个通过
类型
模块检查
@staticmethod
的解决方案(
isinstance(foo,types.UnboundMethodType)
False
如果
foo
是静态的,请参见),但没有找到任何关于如何为
@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
,您应该使用它。它之所以有效,是因为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
>>类Foo:
...     @类方法
...     def bar():
...             通过
...     def baz():
...             通过
...
>>>美食酒吧
>>>福巴兹
>>>类型(食物棒)
>>>类型(Foo.baz)
>>>进口检验
>>>检查方法(食物棒)
真的
>>>检验方法(食品添加剂)
假的
如果对象是一个方法对象,因此有一个属性,并且该属性是从中获取属性的类,那么它将把该类作为第一个参数。它已绑定到该类

请注意,此时您已经有一个绑定对象,因此不需要再次传入该类,除非您首先从
方法中提取原始函数

下面是一个示例,类
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
这应该适用于像
classmethod
那样工作的任何自定义描述符

如果您需要确定该方法是由
classmethod
对象生成的,则需要直接在类名称空间(
cls.\uu dict\uuu
vars(cls)
)中查找属性,并在类层次结构中的每个类中进行查找:

使用一个基类和一个派生类对上述两种方法进行全面测试,使用一个自定义描述符绑定函数,与
classmethod
绑定函数的方式相同,但其本身不是
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()
函数正确区分了
classmethod
notclassmethod
描述符


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

这对我来说很有用:

class Foo(object):
    @classmethod
    def baaz(cls):
        print "baaz"

isinstance(Foo.__dict__["baaz"], classmethod)
def is_classmethod(method):
    """
    Is method a classmethod?
    """
    return isinstance(getattr(method, '__self__', None), type)

它基本上测试
方法。\uuuuu self\uuuu
是否存在并且是一个类,如Martijn的答案所示,但不需要访问类本身。

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

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和Python 3。

这仅适用于Python 3是否有任何东西同时适用于Python 2和Python 3(我个人使用的是2.7)@hlt您可以在下面检查我的答案。这似乎是一种泛型。我们不能保证类型在所有情况下都是classmethod。更好的方法是使用_uname_uu属性而不是字符串作为name。在python中,我们尽可能避免松散的字符串。isinstance(Foo.uu dict_uu[Foo.baaz.uuu name_uuu],classmethod)@SaimRaza虽然我完全同意您的评论,但您必须注意,该方法的
\uu name\uuuuu
可能不一定与属性名匹配。由于我们可以从
dir(cls)
(或者,在OP示例中,直接从
cls.\uuu dict\uuuu
)获取属性名,因此使用属性名比使用方法的规范名更安全。您能用一个示例详细说明一下吗?我假设我们的范围仅限于方法。你指的是Python3中的u_name_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu和_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,ie
Foo.bar=baaz
(其中
Foo
是一个类,而
baaz
是一个函数)。不,我说的不是
\uuuuuu qualname\uuuuu
,而是属性名(
bar
,在上面的例子中)与函数的
\uuuu name\uuuuuuuz
属性值(
baaz
)的比较。
inspect.ismethod(cls.method)
在Python 3.5+中为False。@fx kirin:no,对于
classmethod
对象,它是
True
。您将它与常规函数一起使用,然后您将在任何Python 3版本中看到
False
,而不仅仅是3.5+。这在我的答案中有说明。@fx kirin:我已经更新了答案,以便更清楚地了解这里的目标,即检测使用
@classmethod
修饰的函数的结果,然后查找该类。
def is_classmethod(method):
    """
    Is method a classmethod?
    """
    return isinstance(getattr(method, '__self__', None), type)
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