在Python 3.6中,我试图在AbstractBaseClass中定义一个属性;我的第一次尝试是这样的(后来我发现我可以省略@staticmethod
):
class AnAbstractClass(ABC):
@property
@staticmethod
@abstractmethod
def my_property():
pass
据我所知,@staticmethod
装饰器不会返回可调用但不同的东西。
(我怀疑这也导致mypy在我的代码中引发返回值类型的错误,但是我无法在较小的代码示例中重现该问题。)
这里发生了什么?
答案 0 :(得分:6)
要了解您收到警告的原因,您需要了解decorators和descriptors。
<强>装修强>
装饰器是一个可调用的,用于替换正在装饰的东西,并将其分配给命名空间中的相同名称。通常,装饰器用于函数和类来添加一些功能,比如类型检查或线程或其他东西,但实际上它们可以返回任何东西。
由于装饰器的输出不必与输入的类型相同,或者执行任何相同的处理,因此装饰器的顺序非常重要。装饰器按照从最靠近函数的那个到列表顶部的那个顺序应用。在您的情况下,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
添加到其参数列表中,而第二个装饰器则执行此操作。没有什么可以阻止你调用装饰器,但IDE警告告诉你,你不会成功调用结果对象。如果您尝试在my_property
的具体实现上访问AnyAbstractClass
,您可能会得到TypeError
告诉您my_property
不接受任何位置参数,但会给出一个,因为property.__get__
会将self
添加到静态方法的参数列表中,该参数列表不接受任何参数。
请注意,将staticmethod
应用于property
的结果也不会对您有所帮助。 property
实例根本不可调用。它完全通过__get__
,__set__
和__del__
方法运行,而staticmethod
则假定您传入可调用方式。
<强>解决方案强>
正如您所发现的那样,staticmethod
和property
并不能很好地融合。就其本质而言,属性应始终了解其运行的实例。执行此操作的正确方法是添加self
参数并允许进行常规方法绑定。
property
和staticmethod
都可以与abstractmethod
一起使用(只要首先应用abstractmethod
),因为它实际上不会改变您的原始功能。实际上,abstractmethod
的文档特别提到property
抽象的getter,setter或deleter使整个属性变得抽象。
<强> TL; DR 强>
staticmethod
返回一个可调用的描述符,但其__get__
方法返回其自身的未绑定版本。 property
创建一个不可调用的描述符,其__get__
方法调用属性的getter。使用结果属性将尝试将self
传递给不接受它的静态方法。