python parent class 'wrapping' child-class methods
Asked Answered
O

4

11

I have the following situation in my python code:

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

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

However, I have cases with several generations of such 'decorators', doing different setup/teardown steps before and after 'runImpl', and currently I am forced to define run(), runImpl() and runImplSingleProcess() in my classes Parent, Child and ChildSingleProcess.

I am looking for a solution of the following form:

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"

In this way, there is almost no need for Child class to be aware of this going on.

There may also be an issue with multiple inheritance. If a Child inherits from Parent1 and Parent2, I honestly don't know what should be the correct behavior.

Does anyone know a good, natural, way of accomplishing this? or am I raping the design here?

Thanks
Yonatan

Oliverolivera answered 14/1, 2010 at 15:19 Comment(5)
The question isn't entirely clear. What would you like func_impl to be? A function defined in parent? A function defined by your caller?Leiker
@wds: func_impl is (the original) Child.runAureliaaurelian
answer is here, agf's reponse: https://mcmap.net/q/720046/-python-wrap-class-methodNyberg
Does this answer your question? Python Wrap Class MethodNyberg
@AllenWang while this more recent question is similar in nature, my core problem is around the wish to repeat the process of "wrapping the method" (decorating, really) for multiple generations of subclassing. This isn't the case in the other question.Oliverolivera
H
3

Don't use inheritance here

Invert your design. Instead of a parent-child implementation which is a "is-a" relationship why not just have a composition so you get a "has-a" relationship? You could define classes which implement the methods you'd like while your previous parent class would be instantiated with those implementation specific classes.

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):
Holsworth answered 14/1, 2010 at 15:26 Comment(2)
What you've implemented is just a decorator with a more clunky interface than Python's built-in decorators.Drawshave
Oh? Separating the implementations from the dependencies will increase flexibility. I'm using the Bridge pattern, en.wikipedia.org/wiki/Bridge_pattern, (GoF) to accomplish this. You can even see a simple Python version in that wikipedia entry.Holsworth
D
1

Yonatan, your question isn't clear! Depending on the situation you could use many different designs.

One solution would be to have explicit setup() and teardown() methods which are called by the run() method before calling runImpl(). This would allow subclasses to wrap/override these as needed.

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()

However, you mentioned multiple inheritance, which means this isn't the direction you should be taking at all.

Your mentioning multiple inheritance and wrapping (as in "decorators") leads me to guess that you want to be able to write different "runner" implementations, each with its own setup/teardown process, while re-using pieces of setup/teardown between different "runners".

If this is the case, you could define resources which know how to setup and teardown themselves, and have each runner would declare which resources it requires. The run() method would run the relevant setup/teardown code of each resource and make them available to the runImpl() method.

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(...)
Drawshave answered 14/1, 2010 at 16:11 Comment(0)
A
1

You can get this:

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"

With:

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 only)

Aureliaaurelian answered 17/5, 2013 at 15:52 Comment(0)
F
0

How about a mixin that wraps the run method?

Consider this:

from functools import partial


class WrappingMixin:  # Formerly Parent
    def __init__(self):
        # noinspection PyUnresolvedReferences
        self.run = partial(self.__wrapper, orig_run=self.run)

    def __wrapper(self, /, orig_run):
        print("preparing for run")
        orig_run()
        print("run done")


class Impl(WrappingMixin):  # Formerly Child
    def __init__(self):
        WrappingMixin.__init__(self)

    def run(self):
        print("child running")


Impl().run()

Will output the same:

preparing for run
child running
run done
Frances answered 18/1, 2023 at 11:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.