2015-07-23 36 views
16

In una precedente domanda di ieri, nei commenti, sono venuto a sapere che in python __code__ atrribute di una funzione è mutabile. Quindi posso scrivere codice come seguePerché __code__ per una funzione (Python) mutabile

def foo(): 
    print "Hello" 

def foo2(): 
    print "Hello 2" 

foo() 
foo.__code__ = foo2.__code__ 
foo() 

uscita

Hello 
Hello 2 

ho provato googling, ma sia perché non ci sono informazioni (dubito fortemente questo), o la parola chiave (__code__) non è facilmente ricercabili , Non sono riuscito a trovare un caso d'uso per questo.

Non sembra del tipo "perché la maggior parte le cose in Python sono mutabili" è una risposta ragionevole o, perché altri attributi di funzioni - __closure__ e __globals__ - sono esplicitamente di sola lettura (da Objects/funcobject.c):

static PyMemberDef func_memberlist[] = { 
    {"__closure__", T_OBJECT,  OFF(func_closure), 
    RESTRICTED|READONLY}, 
    {"__doc__",  T_OBJECT,  OFF(func_doc), PY_WRITE_RESTRICTED}, 
    {"__globals__", T_OBJECT,  OFF(func_globals), 
    RESTRICTED|READONLY}, 
    {"__module__", T_OBJECT,  OFF(func_module), PY_WRITE_RESTRICTED}, 
    {NULL} /* Sentinel */ 
}; 

Perché __code__ può essere scritto mentre altri attributi sono di sola lettura?

+1

Non è possibile impedire qualsiasi tipo di "nefandezza" in Python, '__code__' o no. Affidati ai moduli usati o non li usi. – user2864740

+1

Sì. Ho trovato che di solito è il caso. Ma ho ancora pensato di chiedere. –

+2

Esercitati in pile sicure invece di collegarti a API sconosciute. – TigerhawkT3

risposta

4

Il fatto è che la maggior parte delle cose in Python sono modificabili. Quindi la vera domanda è: perché __closure__ e __globals__non?

La risposta inizialmente sembra semplice. Entrambe queste cose sono contenitori di variabili di cui la funzione potrebbe aver bisogno. L'oggetto codice stesso non porta con sé le sue variabili closed-over e globali; semplicemente sa come ottenerli dalla funzione. Prende i valori attuali da questi due attributi quando viene chiamata la funzione.

Ma gli ambiti sono mutevoli, quindi questa risposta non è soddisfacente. Dobbiamo spiegare perché la modifica di queste cose, in particolare, spezzerebbe le cose.

Per __closure__, possiamo guardare alla sua struttura. Non è una mappatura, ma una tupla di cellule. Lo non conosce i nomi delle variabili chiuse. Quando l'oggetto codice cerca una variabile chiusa, deve conoscere la sua posizione nella tupla; si abbinano one-to-one con co_freevars che è anche di sola lettura. E se la tupla è della dimensione sbagliata o meno una tupla, questo meccanismo si rompe, probabilmente violentemente (leggi: segfaults) se il codice C sottostante non si aspetta una tale situazione. Forzare il codice C per verificare il tipo e la dimensione della tupla è inutile lavoro occupato che può essere eliminato rendendo l'attributo di sola lettura. Se si tenta di sostituire __code__ con qualcosa che richiede un diverso numero di variabili libere, you get an error, quindi la dimensione è sempre corretta.

Per __globals__, la spiegazione è meno immediatamente ovvia, ma speculerò. Il meccanismo di ricerca dell'ambito prevede di avere accesso allo spazio dei nomi globale in ogni momento. In effetti, il bytecode può essere hard-coded per andare direttamente allo spazio dei nomi globale, se il compilatore può provare che nessun altro spazio dei nomi avrà una variabile con un nome particolare. Se lo spazio dei nomi globale era improvvisamente None o qualche altro oggetto non mappato, il codice C poteva, ancora una volta, comportarsi in modo violento. Ancora una volta, eseguire il codice eseguendo controlli di tipo inutili sarebbe uno spreco di cicli della CPU.

Un'altra possibilità è che le funzioni (normalmente dichiarate) borrow a reference nello spazio dei nomi globale del modulo e rendendo l'attributo scrivibile causerebbe il conteggio del conteggio dei riferimenti.Potrei immaginare questo disegno, ma non sono sicuro che sia una grande idea, dal momento che le funzioni possono essere costruite in modo esplicito con oggetti la cui durata potrebbe essere inferiore a quella del modulo proprietario, e questi avrebbero bisogno di essere dotati di un involucro speciale.

+0

Non compro la spiegazione di '__closure__': aggiungere un setter che faccia qualche tipo di controllo sarebbe facile (vedi, ad esempio, il setter per' __code__', che fa esattamente questo: https://github.com/python/ cpython/blob/master/Objects/funcobject.C# L254) –

+0

@DavidWolever: è ancora una delle * meno * interfacce user-friendly disponibili. Devi ottenere l'ordine variabile a destra, e non è una tupla di oggetti normali ma una tupla di "celle". Forse il codice di controllo del tipo è semplicemente più difficile di quello che vale, nelle menti degli sviluppatori principali. – Kevin

+0

Io sicuramente non compro l'argomento '__globals__', considerando che' g = {k: v per k, v in func .__ globals __. Items() se k in consentito}; new_func = types.FunctionType (func .__ code__, g, func .__ nome__, func .__ defaults__, func .__ closure __) 'è valido python – NightShadeQueen