Python:装饰一个在继承时打算被覆盖的类方法

Posted

技术标签:

【中文标题】Python:装饰一个在继承时打算被覆盖的类方法【英文标题】:Python: Decorating a class method that is intended to be overwritten when inherited 【发布时间】:2013-09-22 10:01:19 【问题描述】:

假设我有一些基类:

class Task:
    def run(self):
        #override this!

现在,我希望其他人继承 Task 并覆盖 run() 方法:

class MyTask(Task):
    def run(self):
        #successful override!

但是,问题是在每个继承 Task 的类的 run() 方法之前和之后都必须发生逻辑。

似乎我可以做到这一点的一种方法是在基类中定义另一个方法,然后调用 run() 方法。但是,我想问,有没有办法用装饰器来实现这一点?这样做最pythonic的方式是什么?

【问题讨论】:

可能另一个进程将导入 Task 子类并调用 run() 方法。不确定这是否能回答您的问题? 你为什么不让它们覆盖run,而是让API调用一些不同的方法来执行之前/之后的内容并调用run本身? 为什么不让来自Taskrun() 做它必须做的事情并调用在派生类中被覆盖的函数,例如do_run()?您仍然只要求派生类实现一个功能。 是的,对于我的用例来说,这些都是绝对可行的选择。我主要想知道是否有一些装饰魔法,或者是否有处理这类事情的标准方式。 【参考方案1】:

正如 cmets 中所建议的,让子类覆盖一个钩子而不是 run 本身可能是最好的:

class Task(object):
    def run(self):
        # before 
        self.do_run()
        # after

class MyTask(Task):
    def do_run(self):
        ...

task = MyTask()
task.run()

不过,这是您可以使用类装饰器的一种方式:

def decorate_run(cls):
    run = getattr(cls, 'run')
    def new_run(self):
        print('before')
        run(self)
        print('after')
    setattr(cls, 'run', new_run)
    return cls


class Task(object): pass

@decorate_run
class MyTask(Task):
    def run(self):
        pass

task = MyTask()
task.run()

# prints:
# before
# after

另一种方法是使用元类。使用元类的优点是不必修饰子类。 Task 可以作为元类的一个实例,然后Task 的所有子类都会自动继承元类。

class MetaTask(type):
    def __init__(cls, name, bases, clsdict):
        if 'run' in clsdict:
            def new_run(self):
                print('before')
                clsdict['run'](self)
                print('after')
            setattr(cls, 'run', new_run)

class Task(object, metaclass=MetaTask):
    # For Python2: remove metaclass=MetaTask above and uncomment below:
    # __metaclass__ = MetaTask
    pass

class MyTask(Task):
    def run(self):
        #successful override!
        pass

task = MyTask()
task.run()

【讨论】:

感谢元类魔法提示,这才是我真正想知道的。看起来我可能会让其他人覆盖一个钩子,但是知道元类是一个选项真的很有帮助。谢谢! 我一直看到 __new__ 用在元类中,而不是 __init__ - 这样更干净。 __new__ 存在于 Python 中的原因是不可变类型可以在允许子类化的同时保持其不变性。 (如果没有__new__ 并且使用了不可变对象__init__,那么您可以通过调用math.pi.__init__(3.0) 之类的东西来更改不可变对象的值!)所以我相信您应该只为那些您希望实例使用的情况保留__new__是不可变的。 元类示例正是我正在寻找的,但它似乎没有按预期执行。我没有看到打印beforeafter。我错过了什么? @Jørgen:声明元类的语法在 Python2 和 Python3 之间发生了变化。如果您使用的是 Python3,请将 metaclass=MetaTask 添加到类基声明中。我已经编辑了上面的帖子以说明我的意思。

以上是关于Python:装饰一个在继承时打算被覆盖的类方法的主要内容,如果未能解决你的问题,请参考以下文章

如何在不复制整个方法的情况下将装饰器添加到 Python 中的继承方法?

Python子类方法从超类方法继承装饰器

有没有办法在继承期间持久化装饰器?

增强一个对象的方法(继承装饰者模式动态代理)

装饰类的继承类方法中的 cls 行为

Swift - 必须被子类覆盖的类方法