Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically change base class of instances at runtime?

This article has a snippet showing usage of __bases__ to dynamically change the inheritance hierarchy of some Python code, by adding a class to an existing classes collection of classes from which it inherits. Ok, that's hard to read, code is probably clearer:

class Friendly:     def hello(self):         print 'Hello'  class Person: pass  p = Person() Person.__bases__ = (Friendly,) p.hello()  # prints "Hello" 

That is, Person doesn't inherit from Friendly at the source level, but rather this inheritance relation is added dynamically at runtime by modification of the __bases__attribute of the Person class. However, if you change Friendly and Person to be new style classes (by inheriting from object), you get the following error:

TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object' 

A bit of Googling on this seems to indicate some incompatibilities between new-style and old style classes in regards to changing the inheritance hierarchy at runtime. Specifically: "New-style class objects don't support assignment to their bases attribute".

My question, is it possible to make the above Friendly/Person example work using new-style classes in Python 2.7+, possibly by use of the __mro__ attribute?

Disclaimer: I fully realise that this is obscure code. I fully realize that in real production code tricks like this tend to border on unreadable, this is purely a thought experiment, and for funzies to learn something about how Python deals with issues related to multiple inheritance.

like image 893
Adam Parkin Avatar asked Mar 02 '12 19:03

Adam Parkin


1 Answers

Ok, again, this is not something you should normally do, this is for informational purposes only.

Where Python looks for a method on an instance object is determined by the __mro__ attribute of the class which defines that object (the M ethod R esolution O rder attribute). Thus, if we could modify the __mro__ of Person, we'd get the desired behaviour. Something like:

setattr(Person, '__mro__', (Person, Friendly, object)) 

The problem is that __mro__ is a readonly attribute, and thus setattr won't work. Maybe if you're a Python guru there's a way around that, but clearly I fall short of guru status as I cannot think of one.

A possible workaround is to simply redefine the class:

def modify_Person_to_be_friendly():     # so that we're modifying the global identifier 'Person'     global Person      # now just redefine the class using type(), specifying that the new     # class should inherit from Friendly and have all attributes from     # our old Person class     Person = type('Person', (Friendly,), dict(Person.__dict__))   def main():     modify_Person_to_be_friendly()     p = Person()     p.hello()  # works! 

What this doesn't do is modify any previously created Person instances to have the hello() method. For example (just modifying main()):

def main():     oldperson = Person()     ModifyPersonToBeFriendly()     p = Person()     p.hello()       # works!  But:     oldperson.hello()     # does not 

If the details of the type call aren't clear, then read e-satis' excellent answer on 'What is a metaclass in Python?'.

like image 111
Adam Parkin Avatar answered Sep 29 '22 02:09

Adam Parkin