@staticmethod возвращаемое значение - PullRequest
0 голосов
/ 14 мая 2018

В Python 3.6 я пытался определить свойство в AbstractBaseClass; моя первая попытка была примерно такой (позже я обнаружил, что могу опустить @staticmethod):

class AnAbstractClass(ABC):
    @property
    @staticmethod
    @abstractmethod
    def my_property():
        pass

Однако PyCharm показывает мне предупреждение на декораторе свойств: PyCharm warning

Насколько я понимаю, декоратор @staticmethod не возвращает вызываемое, а что-то другое. (Я подозреваю, что это также приводит к тому, что mypy вызывает ошибку в типе возвращаемого значения в моем коде, однако я не могу воспроизвести проблему в меньшем примере кода).

Что здесь происходит?

1 Ответ

0 голосов
/ 14 мая 2018

Чтобы понять, почему вы получаете предупреждение, которое вы получаете, вам нужно немного разбираться в дескрипторах и .

Декораторы

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

Поскольку выходные данные декоратора не обязательно должны быть того же типа, что и входные данные, или выполнять какую-либо одну и ту же обработку, порядок декораторов очень важен. Декораторы применяются по порядку от ближайшего к функции до верхнего в списке. В вашем случае abstractmethod, затем staticmethod, затем property.

дескрипторы

Дескрипторы определяют довольно сложный протокол, который позволяет настраивать объекты с использованием поведения привязки, которое они обеспечивают. Для ваших целей вам нужно знать, что функции являются дескрипторами, и что размещение их в объекте класса использует это. Когда вы вызываете любой дескриптор, который определен в классе в экземпляре этого класса, протокол дескриптора связывает дескриптор с экземпляром, используя метод дескриптора __get__. Сам дескриптор даже не должен быть вызываемым, и также не является возвращаемым значением __get__, хотя в большинстве случаев ожидается, что оно будет. Для функции __get__ возвращает замыкание, которое автоматически передает self в качестве первого позиционного аргумента.

Например, если дан класс A с методом def b(self, arg): и экземпляр этого класса с именем a, выполнение a.b(arg) превращается в A.b.__get__(a, A)(arg). Таким образом, хотя b определено с двумя позиционными аргументами, ему требуется только один, чтобы явно передаваться при вызове экземпляра. Однако, когда вы вызываете b через класс, например, A.b(a, arg), это обычная функция, и вам нужно передать все параметры вручную, включая self.

Собираем все вместе

abstractmethod, property и staticmethod - все декораторы, которые возвращают вызываемые объекты дескриптора. Однако метод их дескрипторов __get__ работает немного иначе, чем __get__ обычного функционального объекта.

abstractmethod создает довольно обычный метод класса, но он взаимодействует с метаклассом ABCMeta, так что вы получаете все виды полезных ошибок при попытке создать экземпляр класса с абстрактными методами. Он никак не изменяет входные параметры, ожидаемые результатом. Фактически, документация намекает на то, что все побочные эффекты могут быть связаны с метаклассом, и что исходный ввод просто проходит. Единственное, что нужно иметь в виду, это

Когда abstractmethod() применяется в сочетании с другими дескрипторами метода, его следует применять как самый внутренний декоратор, как показано в следующих примерах использования: ... *

Ваш код, похоже, следует этому запрету. На самом деле abstractmethod не имеет ничего общего с вашим предупреждением, но в любом случае было бы неплохо упомянуть его здесь.

staticmethod возвращает вызываемый объект, который обходит обычное поведение привязки, чтобы создать метод, которому не важно, из какого класса или экземпляра он вызывается. В частности, метод, связанный с использованием staticmethod.__get__, передаст свои аргументы в вашу функцию, вместо того, чтобы предварительно добавлять self (т. Е. __get__ в основном просто возвращает вашу исходную функцию). Вы можете себе представить, что это будет проблемой для чего-то, что ожидает получения параметра self, например, для установщика свойства.

В отличие от abstractmethod и staticmethod, property создает дескриптор данных. Это означает, что он возвращает объект, имеющий привязку __get__ и привязку __set__ (а также привязку __del__). Метод __get__ свойства работает почти так же, как метод __get__ обычной функции, но применяется специально к функции-получателю. property очень волнует, из какого экземпляра он вызывается, потому что, конечно, вы хотите, чтобы разные экземпляры имели разные значения атрибута, который переносит свойство.

Итак, в вашем коде есть staticmethod, за которым следует property. Первый декоратор возвращает функцию, которая не добавляет self к своему списку аргументов при привязке, в то время как вторая ожидает такую, которая это делает. Ничто не мешает вам вызывать декораторы, но предупреждение IDE говорит вам, что вам не удастся вызвать результирующий объект. Если вы попытаетесь получить доступ к my_property в конкретной реализации AnyAbstractClass, вы, вероятно, получите TypeError, сообщающий вам, что my_property не принимает никаких позиционных параметров, но он был задан, потому что property.__get__ будет предшествовать self в список аргументов статического метода, который не принимает никаких аргументов.

Имейте в виду, что применение staticmethod к результату property также не поможет вам. Экземпляр property вообще не вызывается. Он работает полностью через свои __get__, __set__ и __del__ методы, в то время как staticmethod предполагает, что вы передаете вызываемый.

Решение

Как вы правильно обнаружили, staticmethod и property плохо смешиваются. Свойство, по самой своей природе, должно всегда знать о экземпляре, на котором оно работает. Правильный способ сделать это - добавить параметр self и разрешить нормальную привязку метода.

И property, и staticmethod хорошо работают с abstractmethod (если сначала применяется abstractmethod), потому что он фактически не меняет исходную функцию. На самом деле в документах abstractmethod конкретно упоминается, что метод получения, установки или удаления абстрактного property делает все свойство абстрактным.

TL; DR

staticmethod возвращает дескриптор, который можно вызвать, но чей метод __get__ возвращает несвязанную версию самого себя. property создает не вызываемый дескриптор, чей метод __get__ вызывает метод получения свойства. Использование результирующего свойства попытается передать self статическому методу, который его не принимает.

...