【问题标题】:Implementing an "after" decorator on an abstract method在抽象方法上实现“后”装饰器
【发布时间】:2020-05-05 14:48:50
【问题描述】:

我正在尝试编写一个抽象基类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?

我当然可以通过创建从非抽象方法A.run 调用的不同抽象方法(例如_run)来实现解决方法,但这不太优雅。

我可以在 2007 年看到 PEP 3124 准确地指定了这个功能,但我找不到任何现代参考。

【问题讨论】:

  • 装饰器将在定义时替换您的函数,因此我认为没有任何干净的方法可以知道您的函数已被装饰(以便您可以将其应用于子类)以某种方式制作“标记”;即向函数或类添加属性以供稍后阅读。您可以使用__init_subclass__ 循环遍历子类上定义的所有方法,然后将装饰器应用于带有标记的方法。我想您还必须保存要使用的装饰器,也许标记可以是装饰器本身。
  • 元类会更容易,你可以让你的元类通过类定义的方法运行并包装满足某些标准的方法(例如命名run),每个子类也会发生这种情况。例如,请参阅here并且使用元类还允许您使用abc.abstractmethod 强制用户在实例化类时确实覆盖正确的方法(否则只会在调用方法时出错),请参阅here

标签: python overloading


【解决方案1】:

如果您不希望用户自己装饰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__(这是the intended use of __init_subclass__)来完成。以这种方式使用元类将阻止您的用户使用元类,这将使您的代码不必要地复杂化。如果您需要管道来发挥更多的魔力,您可以使用它们(例如,如果您需要访问__new__)。

我会使用__init_subclass__ 或类装饰器(@pipe 或其他东西),大概也将BA 混合在一起。正如alkasm 所提到的,您可以让A 继承自abc.ABC 并用abc.abstractmethod 装饰run 以确保子类实现它。

【讨论】:

  • 我同意,我认为__init__subclass__ 是这里的方法,因为它是最有意义的,并且是处理任何类型的抽象类初始化/父级更改的最干净的方法
【解决方案2】:

不要覆盖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!

【讨论】:

  • 我同意 - 但我认为 OP 想避免这种情况,正如他们在问题中所说的那样。这是为了简单起见的方法(这总是好的)。
猜你喜欢
  • 2013-10-20
  • 2016-10-28
  • 2013-02-25
  • 2021-09-17
  • 2020-03-29
  • 1970-01-01
  • 2018-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多