3

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

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

但是 PyCharm 向我显示了关于属性装饰器的警告: PyCharm 警告

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

这里发生了什么?

4

1 回答 1

6

要了解您收到警告的原因,您需要了解一下装饰器描述符

装饰器

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

由于装饰器的输出不必与输入的类型相同,也不必执行任何相同的处理,因此装饰器的顺序非常重要。装饰器的应用顺序是从最接近函数的那个​​到列表顶部的那个。在您的情况下,abstractmethod,然后staticmethod,然后property

描述符

描述符定义了一个相当复杂的协议,允许使用它们提供的绑定行为来定制对象。出于您的目的,您需要知道函数是描述符,并且将它们放入类对象中使用 this。当您在该类的实例上调用该类中定义的任何描述符时,描述符协议使用描述符的__get__方法将该描述符绑定到该实例。描述符本身甚至不必是可调用的, 的返回值也不是__get__,尽管在大多数情况下它应该是。对于函数,返回一个自动作为第一个位置参数__get__传递的闭包。self

例如,给定一个A具有方法的类def b(self, arg):,以及该类的一个实例,称为a,doinga.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返回一个绕过正常绑定行为的可调用对象,以创建一个不关心从哪个类或实例调用它的方法。特别是,绑定 using 的方法staticmethod.__get__会将其参数传递给您的函数,而不是self先添加(即,__get__基本上只是返回您的原始函数)。你可以想象这对于期望接收self参数的东西来说是个问题,比如属性的设置器。

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

因此,您的代码中的内容staticmethod后面是property. 第一个装饰器返回一个在绑定时不self添加到其参数列表的函数,而第二个装饰器期望有一个。没有什么可以阻止您调用装饰器,但是 IDE 警告告诉您,您不会成功调用结果对象。如果您尝试访问my_property的具体实现AnyAbstractClass,您可能会收到一条不接受任何位置参数的TypeError通知,但给出了一个,因为它会添加到不接受任何参数的静态方法的参数列表中。my_propertyproperty.__get__self

请记住,申请staticmethod结果property也对您没有太大帮助。property实例根本不可调用。它完全通过它的__get__,__set____del__方法运行,同时staticmethod假设你传入一个可调用对象。

解决方案

正如你已经正确发现的那样staticmethodproperty不要混合得很好。就其本质而言,属性应该始终了解它所操作的实例。这样做的正确方法是添加一个self参数并允许进行正常的方法绑定。

两者都property可以staticmethod很好地使用abstractmethod(只要abstractmethod先应用),因为它实际上不会改变您的原始功能。事实上,文档abstractmethod特别提到抽象的 getter、setter 或 deleterproperty使整个属性抽象。

TL;博士

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

于 2018-05-14T15:49:24.563 回答