请考虑以下示例代码:
from abc import ABC, abstractmethod, abstractproperty
class Base(ABC):
@abstractmethod
def foo(self) -> str:
print("abstract")
@property
@abstractmethod
def __name__(self) -> str:
return "abstract"
@abstractmethod
def __str__(self) -> str:
return "abstract"
@property
@abstractmethod
def __add__(self, other) -> str:
return "abstract"
class Sub(Base):
def foo(self):
print("concrete")
def __str__(self):
return "concrete"
def __add__(self, other) -> str:
return "concrete"
sub = Sub()
sub.foo()
sub.__name__
print(str(sub))
请注意,子类不实现抽象属性__name__
,实际上当引用__name__
时,它会打印为" abstract"来自其父母:
>>> sub.foo()
concrete
>>> sub.__name__
'abstract'
>>> print(str(sub))
concrete
但是,这不是因为__name__
是dunder方法,也不是因为@property
和@abstractmethod
装饰器的某些问题不能很好地协同工作,因为如果我删除了{的实现来自__add__
的{1}},它不允许我实例化它。 (我知道Sub
通常不是属性,但我想使用真正的“dunder”方法。如果删除__add__
和{{__str__
的实现,则会出现相同的预期行为1}}。只有foo
才会这样。
导致此行为的__name__
是什么?有没有办法解决这个问题,还是我需要手动为父(抽象)实现提升__name__
?
答案 0 :(得分:1)
通过__name__
上的数据描述符,类具有type
属性:
>>> Sub.__name__
'Sub'
>>> '__name__' in Sub.__dict__
False
它是一个数据描述符,因为它还拦截了赋值以确保该值是一个字符串。实际值存储在C结构的插槽中,描述符是该值的代理(因此在类上设置新值也不会向__dict__
添加新条目):< / p>
>>> Sub.__name__ = None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign string to NewName.__name__, not 'NoneType'
>>> Sub.__name__ = 'NewName'
>>> Sub.__name__
'NewName'
>>> '__name__' in Sub.__dict__
False
(实际上访问该描述符而不触发它__get__
实际上是不可能的,因为type
本身没有__dict__
并且自己有__name__
。< / p>
这会导致在创建Sub
实例时对属性的测试成功,类毕竟具有该属性:
>>> hasattr(Sub, '__name__')
True
在Sub
的实例上,然后找到Base.__name__
实现,因为实例描述符规则只考虑类和基类,而不是元类型。