静态方法返回值

3
在Python 3.6中,我尝试在抽象基类(AbstractBaseClass)中定义一个属性。我的第一次尝试如下(后来我发现可以省略@staticmethod):
class AnAbstractClass(ABC):
    @property
    @staticmethod
    @abstractmethod
    def my_property():
        pass

然而,PyCharm在属性装饰器上向我显示了一个警告: PyCharm warning 据我所知,@staticmethod 装饰器不返回可调用的内容,而是其他东西。 (我怀疑这也导致 mypy 在我的代码中引发了返回值类型的错误,但我无法在较小的代码示例中复现此问题)。
这里发生了什么?

2
你如何期望静态属性工作?你可能需要查看在元类上定义属性。 - Mad Physicist
装饰器的顺序很重要吗? - user1781434
后来我移除了 @staticmethod 装饰器,将 self 添加到函数签名中,现在一切都按预期工作。 - entropiae
@Tobias。我已经发布了一个答案,解释了为什么无论您如何排列propertystaticmethod,它都不起作用。 - Mad Physicist
@MadPhysicist 很好 - 我以为我已经足够理解描述符了,但显然不是这样。 - user1781434
显示剩余5条评论
1个回答

6
为了理解你收到的警告信息,你需要了解一些关于装饰器描述符的内容。 装饰器 装饰器是一个可调用对象,它可以替换它所装饰的对象,并将其分配给同一命名空间中的相同名称。通常情况下,装饰器被用于函数和类上添加一些功能,比如类型检查、线程等等,但实际上它们可以返回任何东西。
由于装饰器的输出不必与输入具有相同的类型或执行任何相同的处理,因此装饰器的顺序非常重要。装饰器从最靠近函数的位置开始应用,到列表顶部位置结束。在你的案例中,先是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, propertystaticmethod都是返回可调用描述符对象的修饰符。然而,它们的描述符的__get__方法与普通函数对象的__get__有些不同。

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

abstractmethod()与其他方法描述符一起使用时,它应该作为最内层的装饰器应用,如下面的使用示例所示:...
你的代码似乎遵循了这个规定。实际上,abstractmethod与你的警告无关,但在这里提到它似乎是一个好主意。 staticmethod返回一个可调用对象,绕过普通的绑定行为,创建一个不关心被调用的类或实例的方法。特别地,使用staticmethod.__get__绑定的方法将其参数传递给你的函数,而不是首先添加self(即,__get__基本上只返回你的原始函数)。你可以想象,对于期望接收self参数的东西(如属性的setter),这将是一个问题。
abstractmethodstaticmethod不同,property创建了一个数据描述符。这意味着它返回一个同时具有__get__绑定、__set__绑定(以及__del__绑定)的对象。属性的__get__方法的工作方式与普通函数的__get__方法非常相似,但是应用于getter函数。因为当然你希望不同的实例拥有包装属性的不同值,所以property非常关心它被调用的实例。
你的代码中使用了 staticmethodproperty 两个装饰器。第一个装饰器返回一个函数,该函数在绑定时不会在其参数列表中添加 self,而第二个装饰器则期望一个需要添加 self 的函数。虽然你可以调用这些装饰器,但是IDE警告告诉你,你将无法成功调用结果对象。如果你尝试在 AnyAbstractClass 的具体实现中访问 my_property,你很可能会得到一个 TypeError,告诉你 my_property 不接受任何位置参数,但却给了一个,因为 property.__get__ 将在静态方法的参数列表中添加 self,而静态方法不接受任何参数。

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

解决方案

正如你所发现的,staticmethodproperty不太兼容。由于其本质,属性应该始终知道它所操作的实例。正确的做法是添加一个self参数,并允许普通方法绑定进行。 propertystaticmethod都能很好地与abstractmethod配合使用(只要先应用abstractmethod),因为它对原始函数几乎没有任何改变。事实上,abstractmethod的文档特别提到property的getter、setter或deleter使整个属性抽象化。 简而言之,staticmethod返回一个可调用的描述符,但其__get__方法返回其自身的未绑定版本。property创建一个不可调用的描述符,其__get__方法调用属性的getter。使用生成的属性将尝试将self传递给不接受它的静态方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接