@staticmethod return 值

@staticmethod return value

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

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

但是 PyCharm 在 属性 装饰器上向我显示警告:

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

这是怎么回事?

要了解您收到警告的原因,您需要了解一下两者 decorators and descriptors

装饰器

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

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

描述符

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

例如,给定一个 class A 和一个方法 def b(self, arg):,以及一个名为 a 的 class 实例,执行 a.b(arg) 变成 A.b.__get__(a, A)(arg)。因此,虽然 b 被定义为具有两个位置参数,但在实例上调用时只需要显式传入一个。但是,当你通过class调用b时,例如A.b(a, arg),它只是一个普通的函数,你需要手动传入所有参数,包括self .

综合起来

abstractmethod, property and staticmethod 都是 return 可调用描述符对象的装饰器。但是,它们的描述符的 __get__ 方法与普通函数对象的 __get__ 有点不同。

abstractmethod 创建了一个相当正常的 class 方法,但是它与 metaclass ABCMeta 交互,所以当你尝试时会得到各种有用的错误使用抽象方法实例化 class。它不会以任何方式修改结果预期的输入参数。事实上,文档暗示 all 副作用可能与 metaclass 有关,并且原始输入只是通过。这里唯一要真正记住的是

When abstractmethod() is applied in combination with other method descriptors, it should be applied as the innermost decorator, as shown in the following usage examples: ...

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

staticmethod return 是一个绕过正常绑定行为的可调用对象,它创建了一个不关心从什么 class 或实例调用它的方法。特别是,使用 staticmethod.__get__ 绑定的方法会将其参数传递给您的函数,而不是先添加 self (即,__get__ 基本上只是 return 是您的原始函数) .你可以想象这对于期望接收 self 参数的东西来说是个问题,比如 属性.

的 setter

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

所以您的代码中有 staticmethod,然后是 property。第一个装饰器 return 是一个函数,它在绑定时不会将 self 添加到它的参数列表中,而第二个装饰器需要这样做。没有什么可以阻止您调用装饰器,但是 IDE 警告告诉您您不会成功调用结果对象。如果您尝试在 AnyAbstractClass 的具体实现上访问 my_property,您可能会得到一个 TypeError 告诉您 my_property 不接受任何位置参数,但给出了一个, 因为 property.__get__ 会将 self 添加到不接受任何参数的静态方法的参数列表中。

请记住,将 staticmethod 应用于 property 的结果也不会对您有太大帮助。 property 实例根本不可调用。它完全通过其 __get____set____del__ 方法运行,而 staticmethod 假定您传入一个可调用对象。

解决方案

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

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

TL;DR

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