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

Bor*_*lik 81 python abstract-class properties decorator

在下面的代码中,我创建了一个基本抽象类Base.我希望所有继承的类都Base提供name属性,所以我创建了这个属性@abstractmethod.

然后我创建了一个Base名为的子类Base_1,它旨在提供一些功能,但仍然是抽象的.没有name属性Base_1,但是python会在没有错误的情况下实现该类的对象.如何创建抽象属性?

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')  
Run Code Online (Sandbox Code Playgroud)

Jam*_*mes 64

Python 3.3以来,修复了一个错误,这意味着property()当应用于抽象方法时,装饰器现在被正确识别为抽象.

python文档:

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...
Run Code Online (Sandbox Code Playgroud)

  • 值得一提顺序吗?/sf/answers/434592091/ (5认同)
  • 我不认为 python 检查该实现实际上是否有 @property 装饰器,它只是检查是否创建了名为“my_abstract_property”的方法。 (2认同)
  • 我不明白。OP 询问是否有一个名为“name”的基类的属性,该属性必须由所有子类实现,但这里的答案是实现抽象函数。甚至“abc”文档也只显示了抽象函数。是否有一种方法可以使类属性/变量以子类必须实现它们的方式抽象?我需要它有一个所有子类都应该实现的“id”变量。 (2认同)

cod*_*ape 42

Python 3.3之前,你无法嵌套@abstractmethod@property.

使用@abstractproperty创建抽象属性(文档).

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass
Run Code Online (Sandbox Code Playgroud)

代码现在引发了正确的异常:

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

  • 实际上这个答案对于年轻的蟒蛇来说是错误的:从3.3开始,`@ abstractproperty`被弃用,而不是像OP那样的组合. (38认同)
  • 来自3.3文档:http://docs.python.org/3/library/abc.html#abc.abstractproperty (10认同)

him*_*219 9

基于上面詹姆斯的回答

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)
Run Code Online (Sandbox Code Playgroud)

并将其用作装饰器

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()
Run Code Online (Sandbox Code Playgroud)


小智 6

如果您希望所需的实例级属性也使用属性@property装饰器,则在抽象类中使用装饰器(按照James 的答案中的建议)是可行的。

如果您不想使用属性装饰器,可以使用super(). 我最终使用了类似__post_init__()from dataclasses 的东西,它获得了实例级属性所需的功能:

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.
Run Code Online (Sandbox Code Playgroud)


Ger*_*ers 6

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"
Run Code Online (Sandbox Code Playgroud)

它还可用于强制您初始化__new____init__方法中的变量

作为另一个示例,当您尝试初始化类时,以下代码将Base_1失败

    class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"
    
    b = Base_1() 
Run Code Online (Sandbox Code Playgroud)

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

  • 也许这个答案很有用,但这不是OP问题所问的内容。您正在定义一个属性,而不是一个属性。 (3认同)
  • 这个解决方案甚至可能是危险的。如果你这样定义它,你就不能指望人们真正实现它。如果你想稍后使用它,它可能会崩溃,而 linter 认为这个属性存在。所以在 Base 类中定义它没有任何优点,只有缺点。 (3认同)

小智 5

例如,您可以使用@abstractmethodand@property或在抽象类@name.setter中定义抽象 getter、setter 和 deleter ,如下所示。*必须是最里面的装饰器,否则会出现错误:@name.deleterPerson@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
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用class扩展Person抽象类,重写class中的抽象 getter、setter 和 deleter ,实例化并调用 getter、setter 和 deleter,如下所示:StudentStudentStudent

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"))
Run Code Online (Sandbox Code Playgroud)

输出:

John
Tom
False
Run Code Online (Sandbox Code Playgroud)

实际上,即使您不重写Student中的抽象设置器和删除器并实例化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
# ...
Run Code Online (Sandbox Code Playgroud)

没有出现错误如下图:

John
Tom
False
Run Code Online (Sandbox Code Playgroud)

但是,如果您不重写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
# ...
Run Code Online (Sandbox Code Playgroud)

出现以下错误:

类型错误:无法使用抽象方法名称实例化抽象类 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
# ...
Run Code Online (Sandbox Code Playgroud)

出现以下错误:

NameError:名称“名称”未定义

并且,if@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
Run Code Online (Sandbox Code Playgroud)

出现以下错误:

AttributeError:“property”对象的属性“ isabstractmethod ”不可写