2011-12-17 12 views
25

È possibile aggiungere una classe base a un'istanza di oggetto (non una classe!) In fase di esecuzione? Qualcosa sulla falsariga di come le Object#extend opere in Ruby:Mixin dinamico di una classe base in un'istanza in 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

Perché downvotare questo senza un commento? Forse perché la parola Ruby appare nel testo? oO –

+1

Uuuuurrgghhh !!!!! Cambiare un'istanza senza cambiare la classe è una ricetta per il disastro. Il modo migliore per farlo è creare una sottoclasse di 'Person' e mescolare' Gentleman' in quello. – katrielalex

+1

@katrielalex: Probabilmente hai ragione nella maggior parte dei casi. Tuttavia, ho bisogno di questa funzionalità perché voglio aggiungere funzionalità a una libreria di terze parti la cui interfaccia non può essere modificata. Dovevo scegliere tra mixins o il pattern proxy, di cui quest'ultimo non mi piace molto. –

risposta

32

Questo definisce dinamicamente una nuova classe GentlePerson, e riassegna p 's classe ad esso:

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 la richiesta, questa modifica p' s basi, ma non modifica la classe originale . Pertanto, altre istanze di Person non sono interessate (e potrebbero generare un AttributeError se sono stati chiamati introduce_self).


Anche se non è stato chiesto direttamente nella questione, aggiungerò per Googler e curiosi, che è anche possibile cambiare dinamicamente le basi di una classe, ma (per quanto ne so) solo se la classe non eredita direttamente da 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

Molto bello. In realtà ho provato ad assegnare a '__class__', ma questo sembra funzionare solo con classi create dinamicamente. Questa è la soluzione che speravo, grazie. –

+0

Bello! _Direttamente_ da 'object' è la chiave iirc; la soluzione standard consiste nel definire 'oggetto di classe (oggetto): pass'. – katrielalex

+0

L'ho inciampato un po 'perché volevo che la classe 'Gentleman' sovrascriva una proprietà della classe' Person'. In quel caso ho cambiato l'ordine in 'type ('GentlePerson', (Gentleman, Person), {})'. Spero che non porti alcun male. – letmaik

5

Anche se è già una risposta, qui è una funzione:

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

È bene avere una soluzione funzionante, ma per favore ricorda che questo uso della magia nera dovrebbe essere ben ponderato, poiché potrebbe essere il percorso verso il diavolo di codice difficile da capire e difficile da correggere. –

7

leggermente più pulito versione:

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

bella soluzione. Lo sto usando per sovrascrivere i metodi su istanze esistenti, nel qual caso l'ordine dei tipi dovrebbe essere '(cls, base_cls)' – miraculixx

Problemi correlati