首页 > 解决方案 > 在抽象方法上实现“后”装饰器

问题描述

我正在尝试编写一个抽象基类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就可以看到这种方式准确地指定了这个功能,但我找不到任何现代参考。

标签: pythonoverloading

解决方案


如果您不希望用户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可能BA. 正如alkasm 所提到的,您可以进行A继承abc.ABC和装饰runabc.abstractmethod确保子类实现它。


推荐阅读