Подтвердить что ты не робот

Почему @staticmethod не сохраняется в классах, когда @classmethod?

Возьмем следующий пример script:

class A(object):
    @classmethod
    def one(cls):
        print("I am class")

    @staticmethod
    def two():
        print("I am static")


class B(object):
    one = A.one
    two = A.two


B.one()
B.two()

Когда я запускаю этот script с Python 2.7.11, я получаю:

I am class
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    B.two()
TypeError: unbound method two() must be called with B instance as first argument (got nothing instead)

Похоже, что декоратор @classmethod сохраняется по классам, но @staticmethod не является.

Python 3.4 ведет себя как ожидалось:

I am class
I am static

Почему Python2 не сохраняет @staticmethod, и есть ли способ обхода?

edit: взятие двух из класса (и сохранение @staticmethod), похоже, работает, но это все еще кажется странным для меня.

4b9b3361

Ответ 1

classmethod и staticmethod являются дескрипторами, и ни один из них не выполняет то, что вы ожидаете, а не только staticmethod.

Когда вы обращаетесь к A.one, он создает связанный метод на A, а затем создавая атрибут B, но поскольку он привязан к A, аргумент cls всегда будет A, даже если вы вызываете B.one (это имеет место как на Python 2, так и на Python 3, это неправильно везде).

Когда вы обращаетесь к A.two, он возвращает необработанный функциональный объект (дескриптор staticmethod не должен делать ничего особенного, кроме предупреждения привязки, которое будет проходить self или cls, поэтому оно просто возвращает то, что он завернулся). Но этот необработанный функциональный объект затем привязывается к B как метод несвязанного экземпляра, потому что без обтекания staticmethod он точно так же, как вы бы определили его как обычно.

Причина, по которой последний работает в Python 3, заключается в том, что Python 3 не имеет понятия о несвязанных методах. Он имеет функции (которые при доступе через экземпляр класса становятся связанными методами) и связанными методами, где Python 2 имеет функции, несвязанные методы и связанные методы.

Несвязанные методы проверяют, что они вызываются с объектом правильного типа, таким образом, ваша ошибка. Обычные функции просто требуют правильного количества аргументов.

Декоратор staticmethod в Python 3 по-прежнему возвращает исходный объект функции, но в Python 3 это прекрасно; поскольку он не является специальным объектом unbound method, если вы вызываете его в самом классе, он просто как функция с именами, а не какой-либо метод. Если вы попытаетесь сделать это, вы увидите проблему:

B().two()

потому что это сделает связанный метод из этого экземпляра B и функции two, передав дополнительный аргумент (self), который two не принимает. В принципе, на Python 3, staticmethod - это удобство, позволяющее вам вызывать функцию на экземплярах без необходимости связывания, но если вы только когда-либо вызываете функцию, ссылаясь на сам класс, это не нужно, потому что это просто простая функция, а не Python 2 "unbound method".

Если у вас есть причина для выполнения этой копии (как правило, я предлагаю наследовать от A, но что бы то ни было), и вы хотите удостовериться, что вы получили дескриптор завернутой версии функции, а не то, что дескриптор дает вам при доступе к A вы обходите протокол дескриптора, напрямую обращаясь к A __dict__:

class B(object):
    one = A.__dict__['one']
    two = A.__dict__['two']

Прямым копированием из словаря класса, магия протокола дескриптора никогда не вызывается, и вы получаете завершенные версии staticmethod и classmethod one и two.

Ответ 2

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: На самом деле это не ответ, но он не вписывается в формат комментария.

Обратите внимание, что с Python2 @classmethod НЕ правильно сохраняется в классах. В приведенном ниже коде вызов B.one() работает так, как если бы он вызывался через класс A:

$ cat test.py 
class A(object):
    @classmethod
    def one(cls):
        print("I am class", cls.__name__)

class A2(A):
    pass

class B(object):
    one = A.one


A.one()
A2.one()
B.one()

$ python2 test.py 
('I am class', 'A')
('I am class', 'A2')
('I am class', 'A')