Python父类“包装”子类方法

11 投票
4 回答
9247 浏览
提问于 2025-04-15 18:01

我在我的Python代码中遇到了以下情况:

class Parent(object):
    def run(self):
        print "preparing for run"
        self.runImpl()
        print "run done"

class Child(Parent):
    def runImpl(self):
        print "child running"

不过,我有一些情况涉及多个“装饰器”,它们在执行‘runImpl’之前和之后会进行不同的设置和清理。目前,我不得不在我的类ParentChildChildSingleProcess中定义run()runImpl()runImplSingleProcess()

我在寻找一种这样的解决方案:

class Parent(object):
    @wrapping_child_call
    def run(self, func_impl, *args, **kwargs)
        print "preparing for run"
        func_impl(*args, **kwargs)
        print "run done"

class Child(Parent):
    def run(self):
        print "child running"

这样一来,Child类几乎不需要知道这些事情的发生。

另外,可能还会有多个继承的问题。如果一个Child同时继承自Parent1Parent2,我老实说不知道应该是什么样的正确行为。

有没有人知道一个好的、自然的方式来实现这个?还是说我在设计上搞错了?

谢谢
Yonatan

4 个回答

1

你可以得到这个:

class Parent(object):
    def run(self, func_impl, *args, **kwargs):
        print "preparing for run"
        func_impl(*args, **kwargs)
        print "run done"

class Child(Parent):
    @wrapped_in_parent_call
    def run(self):
        print "child running"

使用:

import functools
class wrapped_in_parent_call(object):
    def __init__(self, func):
        self.func = func
    def __get__(self, obj, type=None):
        @functools.wraps(self.func)
        def wrapped(*args, **kwargs):
            owning_class = self.func.__get__(obj, type).im_class
            parent_func = getattr(super(owning_class, obj), self.func.__name__)
            return parent_func(
                lambda *a, **kw: self.func(obj, *a, **kw),
                *args,
                **kwargs
            )

        return wrapped

(仅适用于Python 2)

1

Yonatan,你的问题不太清楚!根据不同的情况,你可以使用很多种不同的设计方案。

一种解决方案是设置明确的setup()和teardown()方法,这些方法会在run()方法中被调用,然后再调用runImpl()。这样,子类就可以根据需要来包装或重写这些方法。

class Runner(object):
    def run(self):
        self.setup()
        self.runImpl()
        self.teardown()
    def setup(self):
        pass
    def teardown(self):
        pass

class RunnerImplementation(Runner):
    def runImpl(self):
        pass # do some stuff
    def setup(self):
        print "doing setup"
        super(RunnerImplementation, self).setup()
    def teardown(self):
        print "doing teardown"
        super(RunnerImplementation, self).teardown()

不过,你提到了多重继承,这意味着你可能不应该朝这个方向去做。

你提到多重继承和包装(就像“装饰器”那样),让我猜测你想要能够编写不同的“运行器”实现,每个实现都有自己的setup和teardown过程,同时又能在不同的“运行器”之间重用一些setup和teardown的部分。

如果是这样的话,你可以定义一些资源,这些资源知道如何进行setup和teardown,然后每个运行器声明它需要哪些资源。run()方法会运行每个资源相关的setup和teardown代码,并将它们提供给runImpl()方法。

class Resource(object):
    name = None # must give a name!
    def setup(self):
        pass
    def teardown(self):
        pass

class DatabaseResource(Resource):
    name = "DB"
    def setup(self):
        self.db = createDatabaseConnection()
    def teardown(self):
        self.db.close()

class TracingResource(Resource):
    name = "tracing"
    def setup(self):
        print "doing setup"
    def teardown(self):
        print "doing teardown"

class Runner(object):
    RESOURCES = []
    def run(self):
        resources = {}
        for resource_class in self.RESOURCES:
            resource = resource_class()
            resource.setup()
            resources[resource_class.name] = resource

        self.runImpl(resources)

        # teardown in opposite order of setup
        for resource in reversed(resources):
            resource.teardown()

class RunnerA(Runner):
    RESOURCES = [TracingResource, DatabaseResource]

    def runImpl(self, resources):
        resources['DB'].execute(...)
3

这里不要使用继承

换个思路来设计。与其用父子类的方式(这是一种“是一个”的关系),不如使用组合的方式,这样就变成了“拥有一个”的关系。你可以定义一些类来实现你想要的方法,而之前的父类可以用这些具体实现的类来创建实例。

class MyClass:
    def __init__(self, impl)
        self.impl = impl
    def run(self,var):
        print "prepare"
        impl.runImpl(var)
        print "I'm done"

class AnImplementation:
    def runImpl(self,var):

撰写回答