Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

extend an instance at runtime

This question was denied on StackExchange, and I haven't found an answer yet on StackOverflow.

Note: the code does work, and I'm asking about ideas, better options and opinions (performance, reliability) to extend class instances at runtime.

For example, let's say my original class is Foo and I want to "extend it" with Bar.

The specific constraints I have now is:

  • Python 2 compatible, even if I'm curious about Python 3 specifics if there are any
  • I get an object from a different library I would like to add functionalities to (note: this is an "old style class")
  • I need it to keep it's parents so isinstance(instance, Foo) keep working
  • It must not re-run the init from the first class (there are stateful connections)
  • It would be nice to be able to test it's inheritance on the "new" class (ie: isinstance(instance, Bar))

I wonder if there is something to do with new, metaclass, or mix-ins. But I did not achieve anything with those yet.

What I tried

Using as a base:

class Foo:
    def foo(self): return 'foo'

class Bar:
    def bar(self): return self.foo() + 'bar'

foo = Foo()

create a new object referring to the previous

It works, but it creates a new object and I wanted to do without doing so (and I think that the test is a useless overhead):

class Bar(Foo, object):
    def __init__(self, foo):
        self.__foo = foo
    def __getattr__(self, name):
        try:
            return getattr(self.__foo, name)
        except:
            raise AttributeError
    def bar(self):
        return self.foo() + 'bar'

bar.foo() # OK
bar.bar() # OK
isinstance(bar, Foo) # OK
isinstance(bar, Bar) # OK

updating the instance dictionary

Almost worked, but Bar does not appear in inheritance:

foo.__class__.__dict__.update(Bar.__dict__)
foo.bar()            # does work
isinstance(foo, Foo) # True
isinstance(foo, Bar) # False :(

using types.MethodType

Almost, but Bar does not appear in inheritance and I have to test if the attribute I'm patching is a callable or not:

for k, v in Bar.__dict__.iteritems():
    if callable(v):
        setattr(foo, k, types.MethodType(v, foo))

foo.foo() # OK
foo.bar() # OK
isinstance(foo, Foo) # OK
isinstance(foo, Bar) # False

Please enlighten me.

like image 532
bufh Avatar asked Oct 15 '25 07:10

bufh


1 Answers

CAVEAT: This is not clean programming! But you knew that :-)

The easiest thing to do is to make Bar be a subclass of Foo, and then dynamically change the type of your instance:

class Foo:
    def foo(self): return 'foo'

class Bar(Foo):
    def bar(self): return self.foo() + 'bar'

foo = Foo()

foo.__class__ = Bar

print foo.bar()

In response to a question in a comment:

>>> class Foo:
...     def foo(self): return 'foo'
... 
>>> class Bar(Foo):
...     def bar(self): return self.foo() + 'bar'
... 
>>> foo = Foo()
>>> 
>>> foo.__class__ = Bar
>>> 
>>> print foo.bar()
foobar
>>> isinstance(foo, Bar)
True
>>> isinstance(foo, Foo)
True

In response to another comment: yes, it really is that easy :)

like image 181
Patrick Maupin Avatar answered Oct 17 '25 19:10

Patrick Maupin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!