2012-01-15 9 views
6

Ho un caso strano e insolito per le metaclassi in cui vorrei modificare lo __metaclass__ di una classe base dopo che è stato definito in modo che le sue sottoclassi utilizzino automaticamente il nuovo __metaclass__. Ma che stranamente non funziona:Perché non posso modificare l'attributo __metaclass__ di una classe?

class MetaBase(type): 
    def __new__(cls, name, bases, attrs): 
     attrs["y"] = attrs["x"] + 1 
     return type.__new__(cls, name, bases, attrs) 

class Foo(object): 
    __metaclass__ = MetaBase 
    x = 5 

print (Foo.x, Foo.y) # prints (5, 6) as expected 

class MetaSub(MetaBase): 
    def __new__(cls, name, bases, attrs): 
     attrs["x"] = 11 
     return MetaBase.__new__(cls, name, bases, attrs) 

Foo.__metaclass__ = MetaSub 

class Bar(Foo): 
    pass 

print(Bar.x, Bar.y) # prints (5, 6) instead of (11, 12) 

quello che sto facendo può benissimo essere imprudente/supportato/indefinito, ma non posso per la vita di me a capire come viene richiamato il vecchio metaclasse, e mi piacerebbe meno capire come sia possibile.

EDIT: Sulla base di una proposta fatta da jsbueno, ho sostituito la linea Foo.__metaclass__ = MetaSub con la seguente riga, che ha fatto esattamente quello che volevo:

Foo = type.__new__(MetaSub, "Foo", Foo.__bases__, dict(Foo.__dict__)) 

risposta

2

Le informazioni sul metaclasse per una classe vengono utilizzate al momento della creazione (analizzate come blocco di classe o dinamicamente, con una chiamata esplicita al metaclasse). Non può essere modificato perché di solito il metaclass apporta modifiche al tempo di creazione della classe - il tipo di classe creato è il metaclasse. L'attributo __metaclass__ è irrilevante una volta creato.

Tuttavia, è possibile creare una copia di una determinata classe e fare in modo che la copia abbia una diversa classe rispetto alla classe originale.

Sul tuo esempio, se invece di fare:

Foo.__metaclass__ = MetaSub

fare:

Foo = Metasub("Foo", Foo.__bases__, dict(Foo.__dict__))

Potrai ottenere ciò che si intendeva. Il nuovo Foo è per tutti gli effetti uguale al suo predecessore, ma con un metaclass diverso.

Tuttavia, le istanze preesistenti di Foo non sarà considerato un esempio della nuova Foo - se avete bisogno di questo, è meglio creare la copia Foo con un nome diverso, invece.

+0

Grazie per il suggerimento, che non ha fatto esattamente quello che volevo, dal momento che voglio ancora sottoclassi di 'Foo' per avere il metaclasse, ma ho modificato la mia domanda per includere la soluzione che ha fatto ciò di cui avevo bisogno, in base al tuo suggerimento. –

3

Il problema è l'attributo __metaclass__ non viene utilizzato quando ereditato, contrariamente a quanto ci si potrebbe aspettare. Il metaclass "vecchio" non viene chiamato né per Bar. La documentazione dicono quanto segue a proposito di come si trova la metaclasse:

Il metaclasse appropriata viene determinata dalle seguenti precedenza regole:

  • Se dict [ '__ metaclass__'] esiste, viene utilizzato.
  • Altrimenti, se esiste almeno una classe base, viene utilizzato il suo metaclasse (questo cerca prima un attributo __class__ e se non trovato, utilizza il suo tipo).
  • Altrimenti, se esiste una variabile globale denominata __metaclass__, viene utilizzata.
  • In caso contrario, viene utilizzata la metaclass classica vecchio stile (types.ClassType).

Quindi, ciò che è effettivamente utilizzato come metaclasse nella classe Bar si trova in attributo del genitore __class__ e non l'attributo del genitore __metaclass__.

Ulteriori informazioni possono essere trovate su this StackOverflow answer.

1

Le sottoclassi utilizzano lo __metaclass__ del genitore.

La soluzione al vostro caso d'uso è di programmare del genitore __metaclass__ in modo che avrà comportamenti diversi per il genitore che per le sue sottoclassi. Forse ha ispezionare il dizionario di classe per una variabile di classe e implementare comportamenti diversi a seconda del suo valore (questa è la tecnica tipo utilizza per controllare se alle istanze viene assegnato un dizionario a seconda del fatto che __slots__ sia definito).

+0

Nel mio esempio attuale, la classe genitore proviene da un modulo di terze parti e voglio estendere una classe da quel modulo, ma ho il mio metaclasse che sovrascrive alcuni dei comportamenti del metaclasse del modulo di terze parti. Altrimenti farei sicuramente ciò che mi suggerisci. –

+0

@EliCourtwright Forse è possibile eseguire il rollover della propria classe che delega al modulo di terze parti anziché ereditare da esso. Questo dovrebbe darti il ​​pieno controllo del processo di creazione della classe massimizzando al contempo il riutilizzo del codice. –

Problemi correlati