Проверьте, использует ли функция @classmethod

TL; DR. Как узнать, была ли определена функция с помощью @classmethod или с тем же эффектом?


Моя проблема

Для реализации декоратора класса я хотел бы проверить, принимает ли метод этот класс в качестве первого аргумента, например, достигнутый с помощью

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

Я нашел решение для проверки @staticmethod через модуль types (isinstance(foo, types.UnboundMethodType)is 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 в этом примере

Ответ 1

Для Python 2 вам нужно проверить оба, если объект является методом, и если __self__ указывает на класс (для обычных методов он будет None при извлечении из класса):

>>> class Foo(object):
...     @classmethod
...     def bar(cls):
...         pass
...     def baz(self):
...         pass
... 
>>> Foo.baz
<unbound method Foo.baz>
>>> Foo.baz.__self__
>>> Foo.baz.__self__ is None
True
>>> Foo.bar.__self__
<class '__main__.Foo'>
>>> Foo.bar.__self__ is Foo
True

В Python 3 регулярные методы отображаются как функции (с помощью несвязанных методов).

Объедините это с inspect.ismethod() для отказобезопасного метода для обнаружения метода класса как в Python 2, так и в 3:

import inspect

if inspect.ismethod(cls.method) and cls.method.__self__ is cls:
    # class method

Атрибут method.__self__ был добавлен в Python 2.6 для соответствия Python 3. В Python 2.6 и 2.7 он является псевдонимом method.im_self.

Ответ 2

Вы должны использовать inspect.ismethod. Он работает, потому что 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

Ответ 3

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

isinstance(Foo.__dict__["baaz"], classmethod)

Ответ 4

Это работает для меня:

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

В основном он проверяет, существует ли method.__self__ и является классом, как в ответе Martijn, но не требует доступа к самому классу.