16

I'm trying to create an object with a run method that will be wrapped by a _wrap_run method. I'd like to be able to call the method and it's wrapper by simply typing instance.run() and I'd like to be able to subclass the object so I can override the run() method and have it still execute the wrapper.

More simply put, I want people to be able to subclass A and override run() but still have calls to the run() method execute the wrapper function.

I'm having some difficulty with the mechanics of this. Does anyone have any suggestions regarding this approach?

class A:

    def run(self):
        print "Run A"
        return True

    def _wrap_run(self):
        print "PRE"
        return_value = self.run()
        print "POST"
        return return_value

    run = property(_wrap_run)


a = A()
a.run()
"""
Should Print: 
PRE
Run A
POST
"""


class B(A):

    def run(self):
        print "Run B"
        return True

b = B()
b.run()
"""
Should Print: 
PRE
Run B
POST
"""
2
  • 1
    What is the point of _wrap_run? Is this supposed to be some kind of decorator? What's wrong with using a decorator for this? Commented Jul 21, 2011 at 18:36
  • 1
    He doesn't want to have to decorate all the derived classes. Commented Jul 21, 2011 at 18:37

5 Answers 5

27

Use a Metaclass.

class MetaClass(type):
    @staticmethod
    def wrap(run):
        """Return a wrapped instance method"""
        def outer(self):
            print "PRE",
            return_value = run(self)
            print "POST"
            return return_value
        return outer
    def __new__(cls, name, bases, attrs):
        """If the class has a 'run' method, wrap it"""
        if 'run' in attrs:
            attrs['run'] = cls.wrap(attrs['run'])
        return super(MetaClass, cls).__new__(cls, name, bases, attrs)

class MyClass(object):
    """Use MetaClass to make this class"""
    __metaclass__ = MetaClass
    def run(self): print 'RUN',

myinstance = MyClass()

# Prints PRE RUN POST
myinstance.run()

Now if other people subclass MyClass, they will still get their run() methods wrapped.

Sign up to request clarification or add additional context in comments.

Comments

5

Easiest way: make run the wrapper, and a private method be the overrideable one.

class A(object):
    def run(self):
        print "PRE"
        return_value = self._inner_run()
        print "POST"
        return return_value

    def _inner_run(self):
        print "Run A"
        return True

class B(A):
    def _inner_run(self):
        print "Run B"
        return True

4 Comments

Thanks for your comment. I'd like to make it so the person implementing run() in the subclass doesn't have to worry about making extra calls and depend on them to properly add the function call. Plus the underscored wrapper function is doing some maintenance work that I'd like to keep 'hidden' from the child class. Though I don't know if what I am asking will work...
@JoeJ: Document the interface properly, and it won't matter if subclass has to override run or something else.
@Joe: it's very common to let user override _run, see FormEncode _to_python for example, instead of run
I think there is more to add if you want to do this more rigorously using functools.wraps to e.g. make sure the original docstrings are copied over.
3

What other folks do

class A:
   def do_run( self ):
       """Must be overridden."""
       raise NotImplementedError
   def run( self, *args, **kw ):
       """Must not be overridden.
       You were warned.
       """
       print "PRE"
       return_value = self.do_run(*args, **kw)
       print "POST"
       return return_value

class B(A):
    def do_run(self):
        print "Run B"
        return True

That's usually sufficient.

If you want to worry about someone "breaking" this, stop now. Don't waste time worrying.

It's Python. We're all adults here. All the malicious sociopaths will break all you code by copying it, changing it, and then breaking it. No matter what you do, they'll just copy your code and modify it to break the clever bits.

Everyone else will read your comment and stick by your rules. If they want to use your module/package/framework, they will cooperate.

4 Comments

run() needs to call do_run() which needs to accept arguments.
Thanks S.Lott. That seems like a strait-forward approach. Ya, I suppose a few well placed comments would be sufficient.
@Joe J: They are sufficient. There's nothing else you can do, really. Anything more complex is just a maintenance headache. And no matter how much extra code you write, someone is always going to mess with it. So write the least code you can get away with.
In your example objects of class A can't be used, only its descendants.
1

Here a while later, but if the method is a normal method, you could just use a regular decorator. Take the example below, which is a simplified SSH connector.

class SSH:

    def __init__(self) -> None:
        self.connected = False

    def connect(self) -> None:
        self.connected = True

    def call_only_if_connected(self) -> None:
        print("foo")

In this example, we only want the method call_only_if_connected to be called, as the name implies, if the SSH object that it is instantiated to has self.connected=True.

We can define a normal wrapper outside the class, and since self, or the instance of the class, is always passed as the first arg (args[0]), we can just make out check there. Consider:

def assert_connected(f):
    @wraps(f)
    def decorator(*args, **kwargs):
        if not args[0].connected:
            raise RuntimeError("attempted to call a method that requires a connection without an established connection")
        return f(*args, **kwargs)
    return decorator

class SSH:

    def __init__(self) -> None:
        self.connected = False

    def connect(self) -> None:
        self.connected = True

    @assert_connected
    def call_only_if_connected(self) -> None:
        print("foo")

Note in the above example that an AttributeError will be thrown if the object does not have a connected property, so it is important to define connected=False as soon as possible (i.e., as soon as you instantiate the object in __init__).

You could handle the improper connection how you'd like; in my example above, I am throwing a RuntimeError.

Comments

0

What you have there is basically a decorator, so why not go ahead and implement _wrap_run as a decorator and apply it when subclassing the function?

5 Comments

I was thinking about making a decorator out of it, but is there a way to do the decorating in the parent class so the person implementing any child classes doesn't have to add the decorator each time?
Nope, that's against what python stands for
I think it's exactly what Python stands for. How is it different than duck typing, or context managers? I can write 'run()' in a straightforward manner, and the metaclass takes care of the details.
@agf well IMO it's not pythonic if someone subclasses your class and it turns out that the function you implemented does not work as expected because of things going on "behind the scenes". Cat Plus Plus's solution, for example, does not do things behind the scenes: the programmer that implements the function knows it will be run as-is, but also that it will be run through another function.
If I write a class that doesn't work in any way, it's not good. Messing up the wrapper function isn't any different from messing up __init__, or __repr__, or any other method that isn't overridden. They are all expected to 'just work' by subclasses.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.