Come cambiare una variabile del modulo da un altro modulo?


106

Supponiamo che io abbia un pacchetto denominato bare che contenga bar.py:

a = None

def foobar():
    print a

e __init__.py:

from bar import a, foobar

Quindi eseguo questo script:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

Ecco cosa mi aspetto:

None
1
1

Ecco cosa ottengo:

None
1
None

Qualcuno può spiegare la mia idea sbagliata?

Risposte:


103

Stai usando from bar import a. adiventa un simbolo nell'ambito globale del modulo di importazione (o qualunque ambito in cui si trova l'istruzione import).

Quando assegni un nuovo valore a a, stai solo cambiando aanche i punti di valore , non il valore effettivo. Prova a importare bar.pydirettamente con import barin __init__.pye conduci lì il tuo esperimento impostando bar.a = 1. In questo modo, modificherai effettivamente bar.__dict__['a']qual è il valore "reale" di ain questo contesto.

È un po 'contorto con tre livelli ma bar.a = 1cambia il valore di anel modulo chiamato da barcui è effettivamente derivato __init__.py. Non cambia il valore di aquella foobarvista perché foobarrisiede nel file effettivo bar.py. Potresti impostare bar.bar.ase volessi cambiarlo.

Questo è uno dei pericoli dell'uso della from foo import barforma importdell'istruzione: si divide barin due simboli, uno visibile globalmente dall'interno fooche inizia puntando al valore originale e un simbolo diverso visibile nello scope in cui importviene eseguita l' istruzione. Cambiare il punto in cui punta un simbolo non cambia anche il valore che puntava.

Questo genere di cose è un killer quando si cerca di reloadun modulo dall'interprete interattivo.


Grazie! La tua risposta probabilmente mi ha appena risparmiato diverse ore di ricerca del colpevole.
jnns

Sembra che anche l' bar.bar.aapproccio non sia di grande aiuto nella maggior parte dei casi d'uso. Probabilmente è un'idea debole mescolare la compilazione o il caricamento del modulo e le assegnazioni di runtime perché finirai per confondere te stesso (o altri che usano il tuo codice). Soprattutto nei linguaggi che si comportano in modo un po 'incoerente al riguardo, come probabilmente fa Python.
matanster

26

Una fonte di difficoltà con questa domanda è che si dispone di un programma di nome bar/bar.py: import barle importazioni sia bar/__init__.pyo bar/bar.py, a seconda di dove lo si fa, che lo rende un po 'ingombrante per tenere traccia che aè bar.a.

Ecco come funziona:

La chiave per capire ciò che accade è rendersi conto che nella tua __init__.py,

from bar import a

in effetti fa qualcosa di simile

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

e definisce una nuova variabile ( bar/__init__.py:a, se lo desideri). Quindi, il tuo from bar import ain __init__.pylega il nome all'oggetto bar/__init__.py:aoriginale bar.py:a( None). Questo è il motivo per cui si può fare from bar import a as a2in __init__.py: in questo caso, è chiaro che avete sia bar/bar.py:aed una distinta nome di variabile bar/__init__.py:a2(nel tuo caso, i nomi delle due variabili appena capita di essere entrambi a, ma ancora vivo in spazi dei nomi diversi: in __init__.py, sono bar.ae a).

Ora, quando lo fai

import bar

print bar.a

stai accedendo alla variabile bar/__init__.py:a(poiché import barimporta il tuo bar/__init__.py). Questa è la variabile che modifichi (a 1). Non stai toccando il contenuto della variabile bar/bar.py:a. Quindi quando lo fai successivamente

bar.foobar()

si chiama bar/bar.py:foobar(), che accede alla variabile ada bar/bar.py, che è ancora None(quando foobar()è definita, lega i nomi delle variabili una volta per tutte, quindi ain bar.pyè bar.py:a, non qualsiasi altra avariabile definita in un altro modulo, poiché potrebbero esserci molte avariabili in tutti i moduli importati ). Da qui l'ultima Noneuscita.

Conclusione: è meglio evitare qualsiasi ambiguità in import bar, non avendo alcun bar/bar.pymodulo (poiché bar.__init__.pyrende bar/già directory un pacchetto, che puoi anche importare con import bar).


12

In altre parole: si scopre che questo malinteso è molto facile da fare. È definito subdolamente nel riferimento al linguaggio Python: l'uso di oggetti invece di simboli . Suggerirei che il riferimento al linguaggio Python lo renda più chiaro e meno scarso ..

Il fromform non lega il nome del modulo: passa attraverso la lista degli identificatori, cerca ognuno di essi nel modulo trovato nel passaggio (1) e lega il nome nel namespace locale all'oggetto così trovato.

PERÒ:

Quando si importa, si importa il valore corrente del simbolo importato e lo si aggiunge al proprio spazio dei nomi come definito. Non stai importando un riferimento, stai effettivamente importando un valore.

Pertanto, per ottenere il valore aggiornato di i, è necessario importare una variabile che contiene un riferimento a quel simbolo.

In altre parole, l'importazione NON è come una dichiarazione in importJAVA, una externaldichiarazione in C / C ++ o anche una useclausola in PERL.

Piuttosto, la seguente dichiarazione in Python:

from some_other_module import a as x

è più simile al codice seguente in K&R C:

extern int a; /* import from the EXTERN file */

int x = a;

(avvertenza: nel caso di Python, "a" e "x" sono essenzialmente un riferimento al valore effettivo: non stai copiando l'INT, stai copiando l'indirizzo di riferimento)


In realtà, trovo che il metodo di Python sia importmolto più pulito di quello di Java, perché gli spazi dei nomi / ambiti devono essere sempre tenuti adeguatamente separati e non interferiscono mai tra loro in modi inaspettati. Come qui: l'alterazione di un'associazione di un oggetto a un nome in uno spazio dei nomi (leggi: assegna qualcosa alla proprietà globale di un modulo) non influisce mai sugli altri spazi dei nomi (leggi: il riferimento che è stato importato) in Python. Ma lo fa in Java, ecc. In Python devi solo capire cosa viene importato, mentre in Java, devi anche capire l'altro modulo nel caso in cui, in seguito, alteri questo valore importato.
Tino

2
Devo dissentire abbastanza strenuamente. L'importazione / inclusione / utilizzo ha una lunga storia di utilizzo del modulo di riferimento e non del modulo valore in quasi tutte le altre lingue.
Mark Gerolimatos

4
Dannazione, premo invio ... c'è un'aspettativa di importazione per riferimento (come da @OP). In effetti, un nuovo programmatore Python, non importa quanto esperto, deve essere detto questo nel modo "attento". Ciò non dovrebbe mai accadere: basato sull'uso comune, Python ha preso la strada sbagliata. Crea un "valore di importazione" se necessario, ma non confondere i simboli con i valori al momento dell'importazione.
Mark Gerolimatos
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.