@staticmethod返回值

时间:2018-05-14 13:31:18

标签: python pycharm python-3.6 abstract-base-class

在Python 3.6中,我试图在AbstractBaseClass中定义一个属性;我的第一次尝试是这样的(后来我发现我可以省略@staticmethod):

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

然而,PyCharm向我展示了一个关于物业装饰者的警告: PyCharm warning

据我所知,@staticmethod装饰器不会返回可调用但不同的东西。 (我怀疑这也导致mypy在我的代码中引发返回值类型的错误,但是我无法在较小的代码示例中重现该问题。)

这里发生了什么?

1 个答案:

答案 0 :(得分:6)

要了解您收到警告的原因,您需要了解decoratorsdescriptors

<强>装修

装饰器是一个可调用的,用于替换正在装饰的东西,并将其分配给命名空间中的相同名称。通常,装饰器用于函数和类来添加一些功能,比如类型检查或线程或其他东西,但实际上它们可以返回任何东西。

由于装饰器的输出不必与输入的类型相同,或者执行任何相同的处理,因此装饰器的顺序非常重要。装饰器按照从最靠近函数的那个​​到列表顶部的那个顺序应用。在您的情况下,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

全部放在一起

abstractmethodpropertystaticmethod都是返回可调用描述符对象的装饰器。但是,它们的描述符的__get__方法与普通函数对象的__get__的工作方式略有不同。

abstractmethod创建一个相当普通的类方法,但它与元类ABCMeta交互,因此当您尝试使用抽象方法实例化类时,会遇到各种有用的错误。它不会以任何方式修改结果所期望的输入参数。事实上,文档暗示了所有副作用可能与元类相关的事实,以及原始输入只是通过。这里唯一要记住的是

  

abstractmethod()与其他方法描述符结合使用时,它应该作为最里面的装饰器应用,如以下用法示例所示:...

您的代码似乎遵循该禁令。实际上abstractmethod与你的警告无关,但无论如何在这里提一下似乎是一个好主意。

staticmethod返回一个绕过正常绑定行为的可调用对象,以创建一个不关心调用它的类或实例的方法。特别是,使用staticmethod.__get__绑定的方法会将其参数传递给您的函数,而不是先放置self(即__get__基本上只返回原始函数)。您可以想象,对于希望接收self参数的内容,例如属性的setter,这会出现问题。

abstractmethodstaticmethod不同,property会创建数据描述符。这意味着它返回一个同时具有__get__绑定和__set__绑定(以及__del__绑定)的对象。属性的__get__方法与普通函数的__get__方法非常相似,但专门应用于getter函数。 property非常关心调用它的实例,因为当然你希望不同的实例具有属性包装的属性的不同值。

因此,代码中的内容为staticmethod,后跟property。第一个装饰器返回一个函数,该函数在绑定时不会将self添加到其参数列表中,而第二个装饰器则执行此操作。没有什么可以阻止你调用装饰器,但IDE警告告诉你,你不会成功调用结果对象。如果您尝试在my_property的具体实现上访问AnyAbstractClass,您可能会得到TypeError告诉您my_property不接受任何位置参数,但会给出一个,因为property.__get__会将self添加到静态方法的参数列表中,该参数列表不接受任何参数。

请注意,将staticmethod应用于property的结果也不会对您有所帮助。 property实例根本不可调用。它完全通过__get____set____del__方法运行,而staticmethod则假定您传入可调用方式。

<强>解决方案

正如您所发现的那样,staticmethodproperty并不能很好地融合。就其本质而言,属性应始终了解其运行的实例。执行此操作的正确方法是添加self参数并允许进行常规方法绑定。

propertystaticmethod都可以与abstractmethod一起使用(只要首先应用abstractmethod),因为它实际上不会改变您的原始功能。实际上,abstractmethod的文档特别提到property抽象的getter,setter或deleter使整个属性变得抽象。

<强> TL; DR

staticmethod返回一个可调用的描述符,但其__get__方法返回其自身的未绑定版本。 property创建一个不可调用的描述符,其__get__方法调用属性的getter。使用结果属性将尝试将self传递给不接受它的静态方法。