为了理解你收到的警告信息,你需要了解一些关于
装饰器和
描述符的内容。
装饰器
装饰器是一个可调用对象,它可以替换它所装饰的对象,并将其分配给同一命名空间中的相同名称。通常情况下,装饰器被用于函数和类上添加一些功能,比如类型检查、线程等等,但实际上它们可以返回任何东西。
由于装饰器的输出不必与输入具有相同的类型或执行任何相同的处理,因此装饰器的顺序非常重要。装饰器从最靠近函数的位置开始应用,到列表顶部位置结束。在你的案例中,先是
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
参数的东西(如属性的setter),这将是一个问题。
与
abstractmethod
和
staticmethod
不同,
property
创建了一个数据描述符。这意味着它返回一个同时具有
__get__
绑定、
__set__
绑定(以及
__del__
绑定)的对象。属性的
__get__
方法的工作方式与普通函数的
__get__
方法非常相似,但是应用于getter函数。因为当然你希望不同的实例拥有包装属性的不同值,所以
property
非常关心它被调用的实例。
你的代码中使用了
staticmethod
和
property
两个装饰器。第一个装饰器返回一个函数,该函数在绑定时不会在其参数列表中添加
self
,而第二个装饰器则期望一个需要添加
self
的函数。虽然你可以调用这些装饰器,但是IDE警告告诉你,你将无法成功调用结果对象。如果你尝试在
AnyAbstractClass
的具体实现中访问
my_property
,你很可能会得到一个
TypeError
,告诉你
my_property
不接受任何位置参数,但却给了一个,因为
property.__get__
将在静态方法的参数列表中添加
self
,而静态方法不接受任何参数。
请记住,对 property
应用 staticmethod
也帮助不大。一个 property
实例根本不可调用。它完全通过其 __get__
、__set__
和 __del__
方法运行,而 staticmethod
假定你传入的是可调用对象。
解决方案
正如你所发现的,
staticmethod
和
property
不太兼容。由于其本质,属性应该始终知道它所操作的实例。正确的做法是添加一个
self
参数,并允许普通方法绑定进行。
property
和
staticmethod
都能很好地与
abstractmethod
配合使用(只要先应用
abstractmethod
),因为它对原始函数几乎没有任何改变。事实上,
abstractmethod
的文档特别提到
property
的getter、setter或deleter使整个属性抽象化。 简而言之,
staticmethod
返回一个可调用的描述符,但其
__get__
方法返回其自身的未绑定版本。
property
创建一个不可调用的描述符,其
__get__
方法调用属性的getter。使用生成的属性将尝试将
self
传递给不接受它的静态方法。
@staticmethod
装饰器,将self
添加到函数签名中,现在一切都按预期工作。 - entropiaeproperty
和staticmethod
,它都不起作用。 - Mad Physicist