我正在尝试编写一个抽象基类A
,它将具有一个抽象方法run
,用户/开发人员将期望重载该方法。我想强制一些"after"行为自动应用于派生类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年看到,PEP3124明确规定了这一功能,但我找不到任何现代参考
如果您不希望用户自己装饰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
混合在一起。正如alkasm所提到的,您可以使A
继承自abc.ABC
,并用abc.abstractmethod
装饰run
以确保子类实现它。
不要覆盖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!