如何在python抽象类中创建抽象属性

fivyi3re  于 2022-11-26  发布在  Python
关注(0)|答案(6)|浏览(247)

在下面的代码中,我创建了一个抽象基类Base。我希望从Base继承的所有类都提供name属性,所以我将此属性设置为@abstractmethod
然后我创建了Base的一个子类,叫做Base_1,它的目的是提供一些功能,但仍然保持抽象。在Base_1中没有name属性,但python却在没有错误的情况下instatinate了该类的一个对象。如何创建抽象属性呢?

from abc import ABCMeta, abstractmethod

class Base(object):
    __metaclass__ = ABCMeta
    def __init__(self, strDirConfig):
        self.strDirConfig = strDirConfig
    
    @abstractmethod
    def _doStuff(self, signals):
        pass
    
    @property    
    @abstractmethod
    def name(self):
        # this property will be supplied by the inheriting classes
        # individually
        pass
    

class Base_1(Base):
    __metaclass__ = ABCMeta
    # this class does not provide the name property, should raise an error
    def __init__(self, strDirConfig):
        super(Base_1, self).__init__(strDirConfig)
    
    def _doStuff(self, signals):
        print 'Base_1 does stuff'
        

class C(Base_1):
    @property
    def name(self):
        return 'class C'
    
        
if __name__ == '__main__':
    b1 = Base_1('abc')
u7up0aaq

u7up0aaq1#

例如,您可以在**Person抽象类**中定义@abstractmethod@property@name.setter@name.deleter的抽象getter、setter和deleter,如下所示。* @abstractmethod必须是最里面的装饰器,否则会出错:

from abc import ABC, abstractmethod

class Person(ABC):

    @property
    @abstractmethod # The innermost decorator
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

然后用**Student扩展Person抽象类**,覆盖**Student中抽象得getter、setter、deleter,示例化Student类**,调用getter、setter、deleter,如下图:

class Student(Person):

    def __init__(self, name):
        self._name = name
    
    @property
    def name(self): # Overrides abstract getter
        return self._name
    
    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

输出量:

John
Tom
False

实际上,即使您不覆盖**Student中的抽象setter和deleter,也可以示例化Student类**,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    @property
    def name(self): # Overrides only abstract getter
        return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name
    
    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

未发生错误,如下所示:

John
Tom
False

但是,如果您不覆盖**Student中的抽象getter、setter和deleter,并示例化Student类**,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name
    
    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

出现以下错误:
TypeError:无法使用抽象方法名称示例化抽象类Student
而且,如果您不覆盖**Student中的抽象getter并示例化Student类**,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
# ...

出现以下错误:
NameError:未定义名称'name'
并且,如果@abstractmethod不是最里面的装饰器,如下所示:

from abc import ABC, abstractmethod

class Person(ABC):

    @abstractmethod # Not the innermost decorator
    @property
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

出现以下错误:
属性错误:“property”对象的特性“isabstractmethod”不可写

gstyhher

gstyhher2#

从Python 3.3开始,一个bug被修复了,这意味着当property()装饰器被应用于一个抽象方法时,它现在被正确地标识为抽象。
注意:顺序很重要,您必须使用@property以上的@abstractmethod

Python 3.3以上版本:(Python文档):

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

Python 2:(Python文档)

from abc import ABC, abstractproperty

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...
oo7oh9g9

oo7oh9g93#

在Python 3.3之前,你不能嵌套@abstractmethod@property
使用@abstractproperty创建抽象属性(docs)。

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass

程式码现在会引发正确的例外状况:

Traceback (most recent call last):
  File "foo.py", line 36, in 
    b1 = Base_1('abc')  
TypeError: Can't instantiate abstract class Base_1 with abstract methods name
dnph8jn4

dnph8jn44#

根据James的上述回答

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)

把它当作装饰品

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()
wdebmtf2

wdebmtf25#

如果您希望所需的示例级属性也使用属性装饰器,则可以在抽象类中使用@property装饰器(如answer by James中所建议的)。
如果你不想使用属性装饰器,你可以使用super()。我最终使用了类似于dataclasses的__post_init__(),它获得了示例级属性所需的功能:

import abc
from typing import List

class Abstract(abc.ABC):
    """An ABC with required attributes.

    Attributes:
        attr0
        attr1 
    """

    @abc.abstractmethod
    def __init__(self):
        """Forces you to implement __init__ in 'Concrete'. 
        Make sure to call __post_init__() from inside 'Concrete'."""

    def __post_init__(self):
        self._has_required_attributes()
        # You can also type check here if you want.

    def _has_required_attributes(self):
        req_attrs: List[str] = ['attr0', 'attr1']
        for attr in req_attrs:
            if not hasattr(self, attr):
                raise AttributeError(f"Missing attribute: '{attr}'")

class Concrete(Abstract):

    def __init__(self, attr0, attr1):
        self.attr0 = attr0
        self.attr1 = attr1
        self.attr2 = "some value" # not required
        super().__post_init__() # Enforces the attribute requirement.
vc9ivgsu

vc9ivgsu6#

在python 3.6+中,你也可以在不提供默认值的情况下对变量进行注解,我发现这是一种更简洁的方法来使其抽象化。

class Base():
    name: str
    
    def print_name(self):
        print(self.name)  # will raise an Attribute error at runtime if `name` isn't defined in subclass

class Base_1(Base):
    name = "base one"

它还可以用于强制您初始化__new____init__方法中的变量
作为另一个示例,当您尝试初始化Base_1类时,以下代码将失败

class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"
    
    b = Base_1()

AttributeError: 'Base_1' object has no attribute 'name'

相关问题