vincent-lg vincent-lg - 4 years ago 96
Python Question

Dynamic decorating with static decorators

So I would like to update a class that is in a library, but I have no control over that class (I can't touch the original source code). Constraint number 2: other users have already inherited this parent class, and asking them to inherit from a third class would be a bit "annoying". So I have to work with both constraints at the same time: needing to extend the parent class, but not by inheritng it.

One solution seemed to make more sense at first, although it's bordering on "monkey-patching". Overriding some methods of the parent class by my own. I wrote a little decorator that could do that. But I met with some error, and rather than giving you the ENTIRE code, here is an example. Consider that the following class, named Old here (the parent class), is in a library I can't touch (regarding its source code, anyway):

class Old(object):

def __init__(self, value):
self.value = value

def at_disp(self, other):
print "Value is", self.value, other
return self.value

That's a simple class, a constructor and a method with a parameter (to test a bit more). Nothing really hard so far. But here comes my decorator to extend a method of this class:

def override_hook(typeclass, method_name):
hook = getattr(typeclass, method_name)
def wrapper(method):
def overriden_hook(*args, **kwargs):
print "Before the hook is called."
kwargs["hook"] = hook
ret = method(*args, **kwargs)
print "After the hook"
return ret
setattr(typeclass, method_name, overriden_hook)
return overriden_hook
return wrapper

@override_hook(Old, "at_disp")
def new_disp(self, other, hook):
print "In the new hook, before"
ret = hook(self, other)
print "In the new hook, after"
return ret

Surprisingly, this works perfectly. If you create an Old instance, and call its at_disp method, the new method is called (and call the old one). Much like hidden inheritance. But here is the real challenge:

We'll try to have a class inheriting from Old. That's what users have done. My "patch" should apply to them too, without needing for them to do anything:

class New(Old):
def at_disp(self, other):
print "That's in New..."
return super(Old, self).at_disp(self, other)

If you create a New object, and try its at_disp method... it crashes. super() cannot find at_disp in Old. Which is odd, because New directly inherits from Old. My guess is that, since my new, replaced method is unbound, super() doesn't find it properly. If you replace super() by a direct call to Old.at_disp(), everything works.

Does somebody know how to fix this issue? And why it happens?

Thanks very much!

Answer Source

Two problems.

First, the call to super should be super(New, self), not super(Old, self). The first argument to super is generally the "current" class (i.e., the class whose method is calling super).

Second, the call to the at_disp method should just be at_disp(other), not at_disp(self, other). When you use the two-argument form of super, you get a bound super object that acts like an instance, so self will automatically be passed if you call a method on it.

So the call should be super(New, self).at_disp(other). Then it works.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download