2011-12-17 11 views
25

¿Es posible agregar una clase base a una instancia de objeto (no una clase) en tiempo de ejecución? Algo a lo largo de las líneas de cómo Object#extend obras en Rubí:Mezcla dinámica en una clase base a una instancia en Python

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" 
+0

¿Por qué downvote esto sin un comentario? Tal vez porque la palabra Ruby aparece en el texto? oO –

+1

Uuuuurrgghhh !!!!! Cambiar una instancia sin cambiar la clase es una receta para el desastre. La mejor manera de hacerlo es crear una subclase de 'Persona' y mezclar' Caballero' en eso. – katrielalex

+1

@katrielalex: Probablemente tengas razón en la mayoría de los casos. Sin embargo, necesito esa funcionalidad porque quiero agregar funcionalidad a una biblioteca de terceros cuya interfaz no puedo cambiar. Tuve que elegir entre mixins o el patrón proxy, del cual este último no me gusta mucho. –

Respuesta

32

Esto define dinámicamente una nueva clase GentlePerson, y reasigna p 'clase s a ella:

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" 

según su petición, esto modifica p' s bases, pero no altera p clase original Person. Por lo tanto, otras instancias de Person no se ven afectadas (y generarían un AttributeError si se llamaran introduce_self).


A pesar de que no se le pidió directamente en la pregunta, voy a añadir para que los Googlers y curiosos, que también es posible cambiar dinámicamente las bases de una clase, sino (que yo sepa) sólo si la clase no hereda directamente de 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 
+0

Muy agradable. De hecho, traté de asignar a '__class__' pero esto parece funcionar solo con clases creadas dinámicamente. Esta es la solución que esperaba, gracias. –

+0

¡Agradable! _Directly_ from 'object' es la clave iirc; la solución estándar es definir 'class object (object): pass'. – katrielalex

+0

Me tropecé un poco porque quería que la clase 'Gentleman' anulara una propiedad de la clase' Person'. Para ese caso cambié el orden a 'type ('GentlePerson', (Gentleman, Person), {})'. Espero que no traiga ningún mal. – letmaik

5

a pesar de que ya ha contestado, aquí es una función:

def extend(instance, new_class): 
    instance.__class__ = type(
      '%s_extended_with_%s' % (instance.__class__.__name__, new_class.__name__), 
      (instance.__class__, new_class), 
      {}, 
     ) 
+0

Es bueno tener una solución de trabajo, pero recuerde que este uso de la magia negra debe sopesarse bien, ya que puede ser el camino al infierno de un código difícil de entender y difícil de depurar. –

7

un poco más limpio versión:

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),{}) 
+1

buena solución. Estoy usando esto para anular los métodos en las instancias existentes, en cuyo caso el orden de los tipos debería ser '(cls, base_cls)' – miraculixx

Cuestiones relacionadas