假设您正在编写一个抽象类,并且它的一个或多个非抽象类方法要求具体类具有特定的类属性;例如,如果可以通过匹配不同的正则表达式来构造每个具体类的实例,您可能希望为 ABC 提供以下内容:
@classmethod
def parse(cls, s):
m = re.fullmatch(cls.PATTERN, s)
if not m:
raise ValueError(s)
return cls(**m.groupdict())
(也许这可以用自定义元类更好地实现,但为了示例而尽量忽略它。)
现在,因为抽象方法和属性的覆盖是在实例创建时检查的,而不是子类创建时,尝试使用 abc.abstractmethod
以确保具体类具有 PATTERN
属性将不起作用 - 但是肯定应该有 一些东西 告诉任何看你的代码的人“我没有忘记在 ABC 上定义 PATTERN
;具体类应该定义它们自己的。”问题是:哪个 是 最 Pythonic 的?
- 一堆装饰器
@property
@abc.abstractmethod
def PATTERN(self):
pass
(顺便说一下,假设 Python 3.4 或更高版本。)这可能会误导读者,因为它暗示 `PATTERN` 应该是实例属性而不是类属性。
- 装饰塔
@property
@classmethod
@abc.abstractmethod
def PATTERN(cls):
pass
这可能会让读者非常困惑,因为 @property
和 @classmethod
通常不能组合;它们只在这里一起工作(对于给定的“工作”值),因为该方法一旦被覆盖就会被忽略。
- 虚拟值
PATTERN = ''
如果具体类无法定义自己的 PATTERN
, parse
将只接受空输入。此选项并不广泛适用,因为并非所有用例都具有适当的虚拟值。
- 错误诱导虚拟值
PATTERN = None
如果具体类未能定义自己的 PATTERN
, parse
将引发错误,程序员得到他们应得的。
没做什么。 基本上是#4 的更硬核变体。 ABC 的文档字符串中某处可能有注释,但 ABC 本身不应有任何妨碍
PATTERN
属性的方式。其他???
原文由 jwodder 发布,翻译遵循 CC BY-SA 4.0 许可协议
您可以使用 Python 3.6 中引入的
__init_subclass__
方法来 更轻松地创建自定义类,而无需借助元类。当定义一个新类时,它被称为创建类对象之前的最后一步。在我看来,使用它的最 pythonic 方式是创建一个类装饰器,它接受属性进行抽象,从而使用户明确知道他们需要定义什么。
回溯可能如下所示,并且发生在子类创建时,而不是实例化时。
在展示装饰器是如何实现之前,先展示一下如何在没有装饰器的情况下实现它是很有启发性的。这里的好处是,如果需要,您可以使基类成为抽象基类,而无需执行任何工作(只需继承自
abc.ABC
或创建元类abc.ABCMeta
)。下面是装饰器的实现方式。