2016-05-24 11 views
6

In questo ben noto answer che spiega metaclasse in Python. Indica che l'attributo __metaclass__ non verrà ereditato.Ereditarietà della metamateriale

Ma, come un dato di fatto, ho provato in Python:

class Meta1(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta1" 
     return type.__new__(cls, clsname, bases, dct) 

# "Using Meta1" printed 
class Foo1: 
    __metaclass__ = Meta1 

# "Using Meta1" printed 
class Bar1(Foo1): 
    pass 

Come previsto, sia Foo e Bar uso Meta1 come metaclasse e stringa di stampa come previsto.

Ma nel seguente esempio, quando type(...) viene restituito al posto del type.__new__(...), la metaclasse non è più ereditato:

class Meta2(type): 
    def __new__(cls, clsname, bases, dct): 
     print "Using Meta2" 
     return type(clsname, bases, dct) 

# "Using Meta2" printed 
class Foo2: 
    __metaclass__ = Meta2 

# Nothing printed 
class Bar2(Foo2): 
    pass 

Ispezione gli attributi __metaclass__ e __class__, posso vedere:

print Foo1.__metaclass__ # <class '__main__.Meta1'> 
print Bar1.__metaclass__ # <class '__main__.Meta1'> 
print Foo2.__metaclass__ # <class '__main__.Meta2'> 
print Bar2.__metaclass__ # <class '__main__.Meta2'> 

print Foo1.__class__ # <class '__main__.Meta1'> 
print Bar1.__class__ # <class '__main__.Meta1'> 
print Foo2.__class__ # <type 'type'> 
print Bar2.__class__ # <type 'type'> 

In conclusione:

  1. Entrambi __metaclass__ e __class__saranno ereditati dalla classe base.

  2. Il comportamento definito dalla creazione Meta2 sarà utilizzato per Foo2, anche se è in realtà Foo2.__class__type.

  3. L'attributo __metaclass__ in Bar2 è Meta2, ma il comportamento di creazione Bar2 non è interessato. In altre parole, utilizza type come metaclasse "reale" anziché Meta2.

Queste osservazioni rendono il meccanismo di trasmissione di tipo __metaclass__ per me indistinto.

La mia ipotesi è che:

  1. Quando si assegna direttamente una classe (ad esempio Meta1) all'attributo __metaclass__ di un'altra classe 'foo1', e 'l'attributo __metaclass__ entrata in vigore.

  2. Se la sottoclasse non imposta esplicitamente l'attributo __metaclass__ durante la definizione. L'attributo __class__ anziché l'attributo __metaclass__ della classe base deciderà il metacllo "reale" della sottoclasse.

La mia ipotesi è corretta? In che modo Python gestisce l'ereditarietà del metaclasse?

+0

Penso che abbia a che fare con type .__ new__ e tipo http://stackoverflow.com/questions/2608708/what-is-the-difference-between-type-and -tipo-new-in-python –

risposta

3

Stai speculando molto, mentre il minimalista di Python e "Casi speciali non sono abbastanza speciali da infrangere le regole". direttiva, renderne più facile la comprensione.

In Python2, un attributo __metaclass__ nel corpo della classe viene utilizzato in fase di creazione della classe per chiamare la "classe" che sarà la classe. Di solito è la classe denominata type.Per chiarire, quel momento è dopo che il parser ha analizzato il corpo della classe, dopo che il compilatore lo ha compilato su un oggetto codice e dopo che è stato effettivamente eseguito al momento dell'esecuzione del programma, e solo se __metaclass__ è esplicitamente fornito nel corpo della classe.

Così modo di controllo che li ha lasciati va in un caso come:

class A(object): 
    __metaclass__ = MetaA 

class B(A): 
    pass 

A ha __metaclass__ nel suo corpo - MetaA è chiamato invece di type di farne "oggetto di classe". B non ha __metaclass__ nel suo corpo. Dopo che è stato creato, se si tenta semplicemente di accedere all'attributo __metaclass__, è un attributo come anyother, che sarà visibile perché Python lo utilizzerà dalla superclasse A. Se si controlla A.__dict__ vedrete il __metaclass__ e se si controlla B.__dict__ no.

Questo attributo A.__metaclass__ è non utilizzato quando viene creato B. Se lo modifichi in A prima di dichiarare B utilizzerai ancora lo stesso metaclasse di A - perché Python usa il tipo della classe genitore come metaclasse nell'assenza della dichiarazione di un esplicito __metaclass__.

Facciamo un esempio:

In [1]: class M(type): pass 

In [2]: class A(object): __metaclass__ = M 

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'> 

In [4]: class B(A): pass 

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B)) 
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'> 

In [6]: A.__metaclass__ = type 

In [8]: class C(A): pass 

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C)) 
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'> 

Inoltre, se si tenta di creare solo una classe attraverso una chiamata a type invece di utilizzare un corpo con una dichiarazione class, __metaclass__ è anche solo un attributo comune:

In [11]: D = type("D", (object,), {"__metaclass__": M}) 

In [12]: type(D) 
type 

Riassumendo fino ad ora: l'attributo __metaclass__ in Python 2 è speciale solo se è inserito esplicitamente nel corpo della classe dichiarazione, come parte dell'esecuzione della dichiarazione di blocco class. È un attributo ordinario senza proprietà speciali in seguito.

Python3 si sono liberati di questo strano "attributo __metaclass__ non valido ora" e hanno consentito un'ulteriore personalizzazione del corpo della classe modificando la sintassi per specificare i metaclassi. (E 'come dichiarato come se si trattasse di un "metaclass nome parametro" sul conto class stesso)

Ora, per la seconda parte di quello sollevato i dubbi: se nel metodo del meta-classe __new__ si chiama type invece di type.__new__, non c'è modo in cui Python possa "sapere" type viene chiamato da una metaclass derivata. Quando chiami type.__new__, passi come primo parametro l'attributo cls del tuo metaclasse __new__ stesso è stato passato dal runtime: questo è ciò che contrassegna la classe risultante come un'istanza di una sottoclasse di type.Questo è proprio come l'ereditarietà funziona per qualsiasi altra classe in Python - così "no comportamenti particolari" qui:

Quindi, individuare la differenza:

class M1(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type.__new__(metacls, name, bases, attrs) 
     # cls now is an instance of "M1" 
     ... 
     return cls 


class M2(type): 
    def __new__(metacls, name, bases, attrs): 
     cls = type(cls, name, bases, attrs) 
     # Type does not "know" it was called from within "M2" 
     # cls is an ordinary instance of "type" 
     ... 
     return cls 

Lo si può vedere nel prompt interattivo:

In [13]: class M2(type): 
    ....:  def __new__(metacls, name, bases, attrs): 
    ....:   return type(name, bases, attrs) 
    ....:  

In [14]: class A(M2): pass 

In [15]: type(A) 
Out[15]: type 

In [16]: class A(M2): __metaclass__ = M2 

In [17]: A.__class__, A.__metaclass__ 
Out[17]: (type, __main__.M2) 

(si noti che il metodo di primo parametro metaclasse __new__ è il metaclasse per sé, quindi, più propriamente chiamato metacls rispetto cls come nel codice, e io na sacco di codice "in the wild")