如何在 python 抽象类中创建抽象属性?
- 2025-04-15 09:20:00
- admin 原创
- 31
问题描述:
在下面的代码中,我创建了一个基抽象类Base
。我希望所有继承自的类都Base
提供该name
属性,所以我将此属性设为@abstractmethod
。
然后我创建了 的子类Base
,名为Base_1
,旨在提供一些功能,但仍然保持抽象。name
中没有属性Base_1
,但 Python 仍然能够毫无错误地实例化该类的一个对象。如何创建抽象属性?
from abc import ABCMeta, abstractmethod
class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
__metaclass__ = ABCMeta
def __init__(self, str_dir_config):
self.str_dir_config = str_dir_config
@abstractmethod
def _do_stuff(self, signals):
pass
@property
@abstractmethod
def name(self):
"""This property will be supplied by the inheriting classes
individually.
"""
pass
class Base1(Base):
__metaclass__ = ABCMeta
"""This class does not provide the name property and should
raise an error.
"""
def __init__(self, str_dir_config):
super(Base1, self).__init__(str_dir_config)
# super().__init__(str_dir_config) <- Python 3
def _do_stuff(self, signals):
print "Base_1 does stuff"
# print("Base_1 does stuff") <- Python 3
class C(Base1):
@property
def name(self):
return "class C"
if __name__ == "__main__":
b1 = Base1("abc")
解决方案 1:
自Python 3.3以来,一个错误被修复,这意味着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 ABCMeta, abstractproperty
class C:
__metaclass__ = ABCMeta
@abstractproperty
def my_abstract_property(self):
...
解决方案 2:
在Python 3.3之前,不能嵌套@abstractmethod
and @property
。
用于@abstractproperty
创建抽象属性(文档)。
from abc import ABCMeta, abstractmethod, abstractproperty
class Base(object):
# ...
@abstractproperty
def name(self):
pass
代码现在引发了正确的异常:
回溯(最近一次调用最后一次):
文件“foo.py”,第 36 行,位于
b1 = Base_1('abc')
TypeError:无法使用抽象方法名称实例化抽象类 Base_1
解决方案 3:
例如,您可以在抽象类@abstractmethod
中使用和@property
或@name.setter
来定义抽象的 getter、setter 和 deleter ,如下所示。 *必须是最内层的装饰器,否则会发生错误:@name.deleter
Person
@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
然后,可以用类扩展Person
抽象类,重写类中的抽象getter,setter和deleter ,实例化类并调用getter,setter和deleter,如下所示:Student
Student
Student
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
类中的抽象设置器和删除器并实例化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
# ...
出现以下错误:
类型错误:无法使用抽象方法名称实例化抽象类 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
出现以下错误:
AttributeError:'property' 对象的属性 ' isabstractmethod ' 不可写
解决方案 4:
根据詹姆斯上述回答
def compatibleabstractproperty(func):
if sys.version_info > (3, 3):
return property(abstractmethod(func))
return abstractproperty(func)
并将其用作装饰器
@compatibleabstractproperty
def env(self):
raise NotImplementedError()
解决方案 5:
如果您希望所需的实例级属性也使用属性装饰器,则使用抽象类中的装饰器(如 James 的回答中所建议的)是可行@property
的。
如果您不想使用属性装饰器,可以使用super()
。我最终使用了类似__post_init__()
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.
解决方案 6:
另一个可能的解决方案是使用元类。
最小示例如下所示:
class BaseMetaClass(type):
def __new__(mcls, class_name, bases, attrs):
required_attrs = ('foo', 'bar')
for attr in required_attrs:
if attr not in attrs:
raise RuntimeError(f"You need to set {attr} in {class_name}")
return super().__new__(mcls, class_name, bases, attrs)
class Base(metaclass=BaseMeta):
foo: str
bar: int
这种方法的一个优点是检查将在定义时进行(而不是实例化)。
此外,在子类中设置类属性比声明属性要容易一些(只要它们是预先知道的简单值),并且最终的类看起来会更简洁
扫码咨询,免费领取项目管理大礼包!