родительский класс python, «оборачивающий» методы дочернего класса - PullRequest
7 голосов
/ 14 января 2010

У меня в коде 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», и мне не нравится определять 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 наследуется от Parent1 и Parent2, я, честно говоря, не знаю, какое должно быть правильное поведение.

Кто-нибудь знает хороший, естественный способ сделать это? или я здесь насилую дизайн?

Спасибо
Йонатан

Ответы [ 3 ]

2 голосов
/ 14 января 2010

Не используйте наследование здесь

Инвертируйте свой дизайн. Вместо реализации типа «родитель-потомок», представляющей собой отношение «есть», почему бы просто не создать композицию, чтобы получить отношение «имеет»? Вы можете определить классы, которые реализуют методы, которые вам нужны, в то время как ваш предыдущий родительский класс будет создан с этими классами, специфичными для реализации.

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):
1 голос
/ 17 мая 2013

Вы можете получить это:

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 голос
/ 14 января 2010

Йонатан, ваш вопрос не ясен! В зависимости от ситуации вы можете использовать много разных конструкций.

Одним из решений было бы иметь явные методы 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()

Однако, вы упомянули множественное наследование, что означает, что это не то направление, в котором вы должны идти.

Ваше упоминание множественного наследования и переноса (как в «декораторах») заставляет меня догадываться, что вы хотите иметь возможность писать разные реализации «бегунов», каждая со своим собственным процессом установки / разрыва, при повторном использовании частей установки / разрыв между "бегунами".

Если это так, вы можете определить ресурсы, которые знают, как настроить и демонтировать сами, и попросить каждого участника объявить, какие ресурсы ему требуются. Метод run () запускает соответствующий код установки / разрыва каждого ресурса и делает их доступными для метода 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(...)
...