progressdll
progressdll

Reputation: 374

How can i add behaviour to a instance of a class without changing the base class

I get an instance of class from a third party library in my code

i = x.get_instance()

My code then calls a method from this instance.

i.method_a() 

which will call a method inside this class where i want to add behaviour.

The only way i found now was to

class BetterClass(ThirdPartyClass):
    def getMessage(self):
        message = super(BetterClass, self).getMessage()
        ... add behaviour
        return message    

i.__class__ = BetterClass
i.method_a()

But what is the better way to add this kind of behaviour as i can't change the instance i get back. I do not init it myself

Upvotes: 4

Views: 580

Answers (3)

Paulo Scardine
Paulo Scardine

Reputation: 77251

Suppose you have a class Foo which has a bar method:

>>> class Foo(object):
...    def __init__(self, name):
...        self.name = name    
...    def bar(self):
...        print "bar", self.name

If you create an instance of this class called foo...

>>> foo = Foo('eggs')

>>> foo.bar()
bar eggs    

...then you want to patch the bar method. First define a function:

>>> def bar(self):
...    self.__class__.bar(self)   # you can call the original method
...    print "spam", self.name

You can patch the instance without monkey-patching the class:

>>> import types

>>> foo.bar = types.MethodType(bar, foo, Foo)

>>> foo.bar()
bar eggs
spam eggs

Not sure if this is right (probably not a good idea) but it works.

The original class is intact:

>>> otherfoo = Foo('foo')

>>> otherfoo.bar()
bar foo

Upvotes: 0

fredtantini
fredtantini

Reputation: 16556

You can do it with:

>>> class Example(object):
...   def foo(self):
...       print "foo"
...
>>> a=Example()
>>> a.foo()
foo
>>>
>>> def new_foo(self):
...    Example.foo(self)
...    print "new"
...
>>> funcType = type(Example.foo)
>>> a.foo = funcType(new_foo, a, Example)
>>> a.foo()
foo
new

Here, type is the class. funcType is then an instancemethod:

>>> funcType
<type 'instancemethod'>
>>> help(funcType)
...
class instancemethod(object)
 |  instancemethod(function, instance, class)
 |
 |  Create an instance method object.

...

Also, (thanks @bruno desthuilliers), you could just do:

a.foo = new_foo.__get__(a, type(a))

instead of using funcType.

Upvotes: 1

bruno desthuilliers
bruno desthuilliers

Reputation: 77892

If you know for sure that x.get_instance() will return an instance of ThirdPartyClass, you can monkeypatch ThirdPartyClass:

from thirdpartlib.module import ThirdPartyClass

def patch_ThirdPartyClass():
    _get_message = ThirdPartyClass.get_message

    def get_message(self):
        message = _get_message()
        # add behaviour here
        return message
    ThirdPartyClass.get_message = get_message

patch_ThirdPartyClass()

You just want to make sure this code will be executed only once per process - if you're not sure you can garantee this, you'd better add some flag:

def patch_ThirdPartyClass():
    _get_message = ThirdPartyClass.get_message
    if getattr(_get_message, "patched", False):
        return

    def get_message(self):
        message = _get_message()
        # add behaviour here
        return message
    get_message.patched = True        
    ThirdPartyClass.get_message = get_message

patch_ThirdPartyClass()

Also you want to make sure this code is executed before any call to x.get_instance(), obviously.

If for any reason you cannot use the above solution, you can still monkeypatch the method on a per-instance basis:

def patch_instance(instance):
    _get_message = instance.get_message

    def get_message(self):
        message = _get_message()
        # add behaviour here
        return message

    instance.get_message = get_message.__get__(instance, type(instance))
    return instance

i = patch_instance(x.get_instance())

Same consideration applies wrt/ making sure you only apply this patch once, so you may want to add a similar flag stuff and test as in the class monkeypatch version.

As a last note: if you have to fallback to the patch_instance solution and want to make sure all calls to x.get_instance() return a patched instance, you may also want to patch x.get_instance so it does the call to patch_instance.

Upvotes: 0

Related Questions