假设您正在编写一个抽象类,并且其一个或多个非抽象类方法要求具体类具有特定的类属性;例如,如果每个具体类的实例可以通过匹配不同的正则表达式来构造,那么您可能希望为您的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
属性的任何内容。
其他???
答案 0 :(得分:9)
Python> = 3.6版本
(向下滚动查看适用于Python的版本< = 3.5)。
如果您有幸只使用Python 3.6而不必担心向后兼容性,则可以使用Python 3.6中引入的新__init_subclass__
方法make customizing class creation easier without resorting to metaclasses。定义新类时,它被称为创建类对象之前的最后一步。
在我看来,使用它的最pythonic方法是创建一个接受属性的类装饰器来制作抽象,从而使用户明确他们需要定义的内容。
from custom_decorators import abstract_class_attributes
@abstract_class_attributes('PATTERN')
class PatternDefiningBase:
pass
class LegalPatternChild(PatternDefiningBase):
PATTERN = r'foo\s+bar'
class IllegalPatternChild(PatternDefiningBase):
pass
回溯可能如下所示,并且发生在子类创建时,而不是实例化时间。
NotImplementedError Traceback (most recent call last)
...
18 PATTERN = r'foo\s+bar'
19
---> 20 class IllegalPatternChild(PatternDefiningBase):
21 pass
...
<ipython-input-11-44089d753ec1> in __init_subclass__(cls, **kwargs)
9 if cls.PATTERN is NotImplemented:
10 # Choose your favorite exception.
---> 11 raise NotImplementedError('You forgot to define PATTERN!!!')
12
13 @classmethod
NotImplementedError: You forgot to define PATTERN!!!
在展示装饰器的实现方式之前,展示如何在没有装饰器的情况下实现它是有益的。这里的好处是,如果需要,你可以使你的基类成为一个抽象的基类而不必做任何工作(只需从abc.ABC
继承或创建元类abc.ABCMeta
)。
class PatternDefiningBase:
# Dear programmer: implement this in a subclass OR YOU'LL BE SORRY!
PATTERN = NotImplemented
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# If the new class did not redefine PATTERN, fail *hard*.
if cls.PATTERN is NotImplemented:
# Choose your favorite exception.
raise NotImplementedError('You forgot to define PATTERN!!!')
@classmethod
def sample(cls):
print(cls.PATTERN)
class LegalPatternChild(PatternDefiningBase):
PATTERN = r'foo\s+bar'
以下是装饰器的实现方式。
# custom_decorators.py
def abstract_class_attributes(*names):
"""Class decorator to add one or more abstract attribute."""
def _func(cls, *names):
""" Function that extends the __init_subclass__ method of a class."""
# Add each attribute to the class with the value of NotImplemented
for name in names:
setattr(cls, name, NotImplemented)
# Save the original __init_subclass__ implementation, then wrap
# it with our new implementation.
orig_init_subclass = cls.__init_subclass__
def new_init_subclass(cls, **kwargs):
"""
New definition of __init_subclass__ that checks that
attributes are implemented.
"""
# The default implementation of __init_subclass__ takes no
# positional arguments, but a custom implementation does.
# If the user has not reimplemented __init_subclass__ then
# the first signature will fail and we try the second.
try:
orig_init_subclass(cls, **kwargs)
except TypeError:
orig_init_subclass(**kwargs)
# Check that each attribute is defined.
for name in names:
if getattr(cls, name, NotImplemented) is NotImplemented:
raise NotImplementedError(f'You forgot to define {name}!!!')
# Bind this new function to the __init_subclass__.
# For reasons beyond the scope here, it we must manually
# declare it as a classmethod because it is not done automatically
# as it would be if declared in the standard way.
cls.__init_subclass__ = classmethod(new_init_subclass)
return cls
return lambda cls: _func(cls, *names)
Python&lt; = 3.5版本
如果您不幸仅仅使用Python 3.6而不必担心向后兼容性,则必须使用元类。虽然这是完全有效的Python,但人们可以讨论解决方案是如何 pythonic 因为元类很难包裹你的大脑,但我认为它会触及The Zen of Python的大多数点,所以我认为这不是那么糟糕。
class RequirePatternMeta(type):
"""Metaclass that enforces child classes define PATTERN."""
def __init__(cls, name, bases, attrs):
# Skip the check if there are no parent classes,
# which allows base classes to not define PATTERN.
if not bases:
return
if attrs.get('PATTERN', NotImplemented) is NotImplemented:
# Choose your favorite exception.
raise NotImplementedError('You forgot to define PATTERN!!!')
class PatternDefiningBase(metaclass=RequirePatternMeta):
# Dear programmer: implement this in a subclass OR YOU'LL BE SORRY!
PATTERN = NotImplemented
@classmethod
def sample(cls):
print(cls.PATTERN)
class LegalPatternChild(PatternDefiningBase):
PATTERN = r'foo\s+bar'
class IllegalPatternChild(PatternDefiningBase):
pass
这与上面显示的Python&gt; = 3.6 __init_subclass__
方法完全相同(除了回溯看起来有点不同,因为它在失败之前通过一组不同的方法路由)。
与__init_subclass__
方法不同,如果要将子类设为抽象基类,则必须执行一些额外的工作(您必须使用ABCMeta
组合元类)
from abs import ABCMeta, abstractmethod
ABCRequirePatternMeta = type('ABCRequirePatternMeta', (ABCMeta, RequirePatternMeta), {})
class PatternDefiningBase(metaclass=ABCRequirePatternMeta):
# Dear programmer: implement this in a subclass OR YOU'LL BE SORRY!
PATTERN = NotImplemented
@classmethod
def sample(cls):
print(cls.PATTERN)
@abstractmethod
def abstract(self):
return 6
class LegalPatternChild(PatternDefiningBase):
PATTERN = r'foo\s+bar'
def abstract(self):
return 5
class IllegalPatternChild1(PatternDefiningBase):
PATTERN = r'foo\s+bar'
print(LegalPatternChild().abstract())
print(IllegalPatternChild1().abstract())
class IllegalPatternChild2(PatternDefiningBase):
pass
输出正如您所期望的那样。
5
TypeError: Can't instantiate abstract class IllegalPatternChild1 with abstract methods abstract
# Then the NotImplementedError if it kept on going.
答案 1 :(得分:3)
我一直在寻找类似的东西,直到昨天我决定深入研究它。我非常喜欢@SethMMorton's reply,但是缺少两件事:允许抽象类具有一个本身是抽象的子类,并与类型提示和诸如mypy之类的静态类型输入工具一起很好地使用(这很有意义,自2017年以来,这些都不是问题。
我开始着手用自己的解决方案在此处写一个回复,但是我意识到我需要大量的测试和文档,因此我将其设置为合适的python模块:abstractcp。
使用(从0.9.5版开始):
console.log("responseGETHtml2", responseGETHtml2.html());
请充分使用page on pypi或github。