Python >= 3.6 版本
(向下滚动查看适用于 Python
如果您有幸只使用 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
如果您不够幸运,只能使用 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 >= 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.