Родительский класс python «обертывает» методы дочернего класса

У меня есть следующая ситуация в моем коде 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() в моих классах Parent, Child и ChildSingleProcess.

Я ищу решение в следующем виде:

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

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

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


person Yonatan    schedule 14.01.2010    source источник
comment
Вопрос не совсем ясен. Каким бы вы хотели видеть func_impl? Функция, определенная в родительском? Функция, определенная вашим вызывающим абонентом?   -  person wds    schedule 14.01.2010
comment
@wds: func_impl это (оригинал) Child.run   -  person Eric    schedule 15.05.2013
comment
ответ здесь, ответ agf: stackoverflow.com/questions/6780907/python -метод-класса-обертки   -  person Allen Wang    schedule 11.03.2020
comment
Отвечает ли это на ваш вопрос? Метод класса переноса Python   -  person Allen Wang    schedule 11.03.2020
comment
@AllenWang, хотя этот более свежий вопрос похож по своей природе, моя основная проблема заключается в желании повторить процесс обертывания метода (на самом деле украшения) для нескольких поколений подклассов. В другом вопросе этого нет.   -  person Yonatan    schedule 13.03.2020


Ответы (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):
person wheaties    schedule 14.01.2010
comment
То, что вы реализовали, — это просто декоратор с более неуклюжим интерфейсом, чем встроенные декораторы Python. - person taleinat; 14.01.2010
comment
Ой? Отделение реализации от зависимостей повысит гибкость. Я использую шаблон Bridge, en.wikipedia.org/wiki/Bridge_pattern, (GoF ), чтобы выполнить это. Вы даже можете увидеть простую версию Python в этой записи в Википедии. - person wheaties; 14.01.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(...)
person taleinat    schedule 14.01.2010

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

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)

person Eric    schedule 17.05.2013