简体   繁体   中英

Abstract Property on __name__ not enforced

Consider the following sample code:

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))

Note that the subclass does not implement the abstract property __name__ , and indeed when __name__ is referenced, it prints as "abstract" from its parent:

>>> sub.foo()
concrete
>>> sub.__name__
'abstract'
>>> print(str(sub))
concrete

However, it is not because __name__ is a dunder method, nor because of some issue with @property and @abstractmethod decorators not working well together, because if I remove the implementation of __add__ from Sub , it does not let me instantiate it. (I know __add__ is not normally a property, but I wanted to use a 'real' dunder method) The same expected behavior occurs if I remove the implementation of __str__ and foo . Only __name__ behaves this way.

What is it about __name__ that causes this behavior? Is there some way around this, or do I need to have the parent (abstract) implementation manually raise the TypeError for me?

Classes have a __name__ attribute by way of a data descriptor on type :

>>> Sub.__name__
'Sub'
>>> '__name__' in Sub.__dict__
False

It's a data descriptor because it also intercepts assignments to ensure that the value is a string. The actual values are stored in a slot on the C structure, the descriptor is a proxy for that value (so setting a new value on the class doesn't add a new entry to the __dict__ either):

>>> 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

(actually accessing that descriptor without triggering it's __get__ is not really possible as type itself has no __dict__ and has itself a __name__ ).

This causes the test for the attribute when creating instances of Sub to succeed, the class has that attribute after all:

>>> hasattr(Sub, '__name__')
True

On instances of Sub , the Base.__name__ implementation is then found because instance descriptor rules only consider the class and base classes, not the metatype.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM