48

Is it possible to add a base class to an object instance (not a class!) at runtime? Something along the lines of how Object#extend works in Ruby:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Person(object):
  def __init__(self, name):
    self.name = name

p = Person("John")
# how to implement this method?
extend(p, Gentleman)
p.introduce_self() # => "Hello, my name is John"
Allan Lewis
  • 308
  • 3
  • 13
Niklas B.
  • 92,950
  • 18
  • 194
  • 224
  • 4
    Uuuuurrgghhh!!!!! Changing an instance without changing the class is a recipe for disaster. The nicer way to do this is to make a subclass of `Person` and mix `Gentleman` into that. – Katriel Dec 17 '11 at 14:01
  • 4
    @katrielalex: Probably you're right in most cases. Nevertheless, I need that functionality because I want to add functionality to a third-party library whose interface I cannot change. I had to choose between mixins or the proxy pattern, of which the latter I don't like very much. – Niklas B. Dec 17 '11 at 14:17

3 Answers3

61

This dynamically defines a new class GentlePerson, and reassigns p's class to it:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Person(object):
  def __init__(self, name):
    self.name = name

p = Person("John")
p.__class__ = type('GentlePerson',(Person,Gentleman),{})
print(p.introduce_self())
# "Hello, my name is John"

Per your request, this modifies p's bases, but does not alter p's original class Person. Thus, other instances of Person are unaffected (and would raise an AttributeError if introduce_self were called).


Although it was not directly asked in the question, I'll add for googlers and curiosity seekers, that it is also possible to dynamically change a class's bases but (AFAIK) only if the class does not inherit directly from object:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Base(object):pass
class Person(Base):
  def __init__(self, name):
    self.name = name

p = Person("John")
Person.__bases__=(Gentleman,object,)
print(p.introduce_self())
# "Hello, my name is John"

q = Person("Pete")
print(q.introduce_self())
# Hello, my name is Pete
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Very nice. I actually tried to assign to `__class__` but this seems to work with dynamically created classes only. This is the solution I was hoping for, thanks. – Niklas B. Dec 17 '11 at 13:50
  • Nice! _Directly_ from `object` is the key iirc; the standard workaround is to define `class object(object): pass`. – Katriel Dec 17 '11 at 14:00
  • 1
    I stumbled it a bit as I wanted the `Gentleman` class override a property of the `Person` class. For that case I switched the order to `type('GentlePerson',(Gentleman,Person),{})`. Hope that doesn't bring any evil. – letmaik Sep 24 '14 at 14:30
  • 2
    @neo: That's fine. `Gentleman` is not a subclass of `Person`, nor vice versa, so either one can come first in the list of bases. – unutbu Sep 24 '14 at 16:56
  • Is there also a way to help type checkers understand what is going on so they don't complain when they see the calls to `introduce_self`? – myke May 06 '23 at 07:23
  • Answering my own question: In some situations a simple solution like `assert isinstance(p, Gentleman)` can be sufficient to help type checkers. – myke May 06 '23 at 08:01
22

Slightly cleaner version:

def extend_instance(obj, cls):
    """Apply mixins to a class instance after creation"""
    base_cls = obj.__class__
    base_cls_name = obj.__class__.__name__
    obj.__class__ = type(base_cls_name, (base_cls, cls),{})
SleepyCal
  • 5,739
  • 5
  • 33
  • 47
  • 5
    nice solution. I'm using this to override methods on existing instances, in which case the type order should be `(cls, base_cls)` – miraculixx Nov 08 '15 at 00:35
8

Although it's already answered, here is a function:

def extend(instance, new_class):
    instance.__class__ = type(
          '%s_extended_with_%s' % (instance.__class__.__name__, new_class.__name__), 
          (instance.__class__, new_class), 
          {},
          )
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
reclosedev
  • 9,352
  • 34
  • 51
  • 3
    It is good to have a working solution but please remember this use of black magic should be well pondered, as it may be the path to the hell of hard-to-understand and hard-to-debug code. – gb. Dec 19 '11 at 03:02