我想写一个抽象的基类。A
它将有一个抽象的方法 run
用户开发者需要重载该类。我想强制执行一些 "之后 "的行为,使其自动应用到派生类中。B
因此,在 B.run()
进程结束后,另一个标准方法将被调用(在数据管道中,这可能是例如提交或回滚事务)。有什么方法可以实现这一点吗?
我失败的天真的尝试是。
def do_the_other_thing(func):
def wrapper():
func()
print('doing the other thing!')
return wrapper
class A:
@do_the_other_thing
def run(self):
pass
class B(A):
def run(self):
print('running!')
B().run()
>>> 'running!'
>>> #'doing the other thing!' # <--- is there a way to get this?
当然,我可以通过创建一个不同的抽象方法来实现一个变通方法(例如 _run
),然后从一个非抽象方法中调用该方法 A.run
但这就不那么优雅了。
我可以看到,早在2007年 PEP 3124 确切地指定了这个功能,但我找不到任何现代的引用。
其实单靠一个函数装饰器是做不到你想要的,如果你不希望这个函数的 用户来装饰 run
自己. 你可以使用类装饰器。__init_subclass__
或 metaclasses
.
有了班级装饰师。
class A:
def run(self):
pass
def do_the_other_thing(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('doing the other thing!')
return wrapper
def pipeline_thing(cls):
cls.run = do_the_other_thing(cls.run)
# do some other work
return cls
@pipeline_thing
class B(A):
def run(self):
print("running!")
或与 __init_subclass__
class A:
def run(self):
pass
def __init_subclass__(cls):
super().__init_subclass__()
cls.run = do_the_other_thing(cls.run)
# do some other work
class B(A):
def run(self):
print("running!")
或与 metaclasses
class AMeta(type):
def __init__(cls, name, bases, attrs, **kwargs):
super().__init__(name, bases, attrs)
cls.run = do_the_other_thing(cls.run)
# do some other work
class A(metaclass=AMeta):
def run(self):
pass
class B(A):
def run(self):
print("running!")
这个例子对于元类来说是矫枉过正的(你使用的是 metaclass.__init__
- 元类中最不强大的魔法方法,你的行为可以用 __init_subclass__
(这是 的用途 __init_subclass__
). 以这种方式使用元类会阻止你的用户使用元类,它会不必要地使你的代码复杂化。如果你需要管道做更多的魔法,你可以使用它们(比如说如果你需要访问 __new__
).
我会使用 __init_subclass__
或类装饰器(@pipe
或什么的),也大概是混合的 B
与 A
. 如: 碱液 所述,你可以使 A
继承 abc.ABC
妆点 run
与 abc.abstractmethod
以确保子类实现它。
不要覆盖 run
;覆盖一个方法,该方法 run
电话.
class A:
def run(self):
self.do_run()
print('doing the other thing!')
def do_run(self):
pass
class B(A):
def do_run(self):
print('running!')
那么
>>> B().run()
running!
doing the other thing!