2015-06-15 9 views
11

Questo in realtà deriva da una discussione qui su SO.Chi chiama la metaclasse

Versione corta

def meta(name, bases, class_dict) 
    return type(name, bases, class_dict) 

class Klass(object): 
    __metaclass__ = meta 

meta() viene chiamato quando viene eseguito Klass dichiarazione di classe.

Quale parte del codice (python internal) in realtà chiama meta()?

Versione lunga

Quando la classe è dichiarata, del codice ha a che fare le opportune verifiche degli attributi e vedere se c'è un __metaclass__ dichiarata su un tipo. Se tale esiste, deve eseguire una chiamata di metodo su quel metaclass con i ben noti attributi (class_name, bases, class_dict). Non è chiaro per me quale codice sia responsabile per quella chiamata.

Ho fatto alcuni scavi in ​​CPython (vedi sotto), ma mi piacerebbe davvero avere qualcosa di più vicino a una risposta definitiva.

Opzione 1: Chiamato direttamente

La chiamata metaclasse è cablato nel parsing di classe. Se è così, ci sono prove per questo?

Opzione 2: E 'chiamato dal type.__new__()

Codice in type_call() chiamate type_new() che a sua volta chiama _PyType_CalculateMetaclass(). Questo suggerisce che la risoluzione metaclasse è in realtà fatto durante la chiamata a type() quando si cerca di scoprire quale valore da restituire da type()

Ciò sarebbe in in linea con l'idea che una "classe" è un "richiamabile che restituisce un oggetto ".

Opzione 3: Qualcosa di diverso

Tutte le mie supposizioni forse completamente sbagliato, ovviamente.

Alcuni casi di esempio che ci siamo inventati in videochat:

Esempio 1:

class Meta(type): 
    pass 

class A: 
    __metaclass__ = Meta 

A.__class__ == Meta 

Questo è ciò che Meta.__new__() ritorni, quindi questo sembra legittimo. Il metaclasse si pone come A.__class__

Esempio 2:

class Meta(type): 
    def __new__(cls, class_name, bases, class_dict): 
     return type(class_name, bases, class_dict) 

class A(object): 
    __metaclass__ = Meta 

A.__class__ == type 

Edit 2: corretta versione iniziale, derivano correttamente Meta da type.

Sembra ok, ma non sono sicuro che faccia ciò che penso. Inoltre: qual è il metodo canonico per farlo comportarsi come nell'esempio 1?

Edit 3: Utilizzo type.__new__(...) sembra funzionare come previsto, che sembra anche a favore dell'opzione 2.

Può qualcuno con più approfondita conoscenza della magia python interno mi illumini?

Modifica: A per un primer abbastanza conciso su metaclassi: http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/. Ha anche alcuni diagrammi davvero interessanti, riferimenti ed evidenzia anche le differenze tra python 2 e 3.

Edit 3: C'è una buona risposta per Python 3. Python 3 usa __build_class__ per creare un oggetto di classe. Il percorso del codice è, in ogni caso, diverso in Python 2.

+1

http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python –

+1

FWIW, la discussione originale riguardava se 'SomeClass()' risolve in 'SomeClass .__ class __.__ call __() '(che alla fine delegherà a' type .__ new __() ', o se iit si risolve a' type.__call __() '(che sarebbe il delegato a' SomeClass .__ class __.__ call__'). –

+0

@brunodesthuilliers Penso che abbiamo risolto quello ed è il primo. Ancora alcuni enigmi si aprono, però :) – dhke

risposta

6

È possibile trovare la risposta con relativa facilità. Innanzitutto, consente di trovare l'opcode per la creazione di una classe.

>>> def f(): 
    class A(object): 
     __metaclass__ = type 

>>> import dis 
>>> dis.dis(f) 
    2   0 LOAD_CONST    1 ('A') 
       3 LOAD_GLOBAL    0 (object) 
       6 BUILD_TUPLE    1 
       9 LOAD_CONST    2 (<code object A at 0000000001EBDA30, file "<pyshell#3>", line 2>) 
      12 MAKE_FUNCTION   0 
      15 CALL_FUNCTION   0 
      18 BUILD_CLASS   
      19 STORE_FAST    0 (A) 
      22 LOAD_CONST    0 (None) 
      25 RETURN_VALUE  

Quindi l'opcode è BUILD_CLASS. Ora cerchiamo la fonte per quel termine (fatto facilmente sul github mirror).

Si ottengono un paio di risultati, ma il più interessante dei quali è Python/ceval.c che dichiara la funzione static PyObject * build_class(PyObject *, PyObject *, PyObject *); e ha un'istruzione case per BUILD_CLASS. Ricerca attraverso il file e si può trovare la definizione della funzione di build_class partire dalla riga 4430. E on line 4456 troviamo il pezzo di codice che sono alla ricerca di:

result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, 
         NULL); 

Quindi la risposta è la metaclasse viene risolto e chiama dalla funzione che è responsabile dell'esecuzione dell'opcode BUILD_CLASS.

+0

Sembra un esempio di python 3 - l'OP ha chiesto come funziona in python 2.7.x –

+1

Il codice proviene da https://github.com/python-git/python che è per 2.7 . Guardate con attenzione la funzione 'build_class' potete vedere che è sicuramente 2.7 in realtà sta cercando il nome' __metaclass__', che viene ignorato in 3.x. – Dunes

+0

Sì, questa è la risposta per Python 2. Stavo per aggiornare la mia risposta per dire approssimativamente la stessa cosa. Continuerò ad aggiornare la mia risposta un po ', ma riceverai un +1 anche da me! – Blckknght

-4

Le metriche vengono "istanziate" dall'interprete quando viene eseguita la definizione della classe.

2

In Python 3, il metaclasse viene chiamato nel codice per la funzione incorporata __build_class__ (che viene chiamata per gestire le istruzioni class). Questa funzione è nuova in Python 3 e l'equivalente funzione C build_class in Python 2 non è esposta pubblicamente a livello di Python. Tuttavia, è possibile trovare la fonte in python/ceval.c

In ogni caso, ecco la chiamata rilevante per l'oggetto metaclasse in Python 3 __build_class__ implementazione:

cls = PyEval_CallObjectWithKeywords(meta, margs, mkw); 

La variabile meta è la metaclasse (sia type o di un altro metaclasse trovato da un argomento o dal tipo di una classe base). margs è una tupla con gli argomenti posizionali (name, bases, dct) e mkw è un dizionario con gli argomenti delle parole chiave per il metaclass (una cosa solo in Python 3).

Il codice Python 2 fa qualcosa di simile:

result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, 
             NULL); 
Problemi correlati