Visibilità delle variabili globali nei moduli importati


112

Mi sono imbattuto in un po 'di difficoltà nell'importazione di moduli in uno script Python. Farò del mio meglio per descrivere l'errore, perché mi imbatto in esso e perché sto legando questo particolare approccio per risolvere il mio problema (che descriverò tra un secondo):

Supponiamo di avere un modulo in cui ho definito alcune funzioni / classi di utilità, che si riferiscono a entità definite nello spazio dei nomi in cui verrà importato questo modulo ausiliario (sia "a" tale entità):

modulo 1:

def f():
    print a

E poi ho il programma principale, dove è definito "a", in cui voglio importare quelle utilità:

import module1
a=3
module1.f()

L'esecuzione del programma attiverà il seguente errore:

Traceback (most recent call last):
  File "Z:\Python\main.py", line 10, in <module>
    module1.f()
  File "Z:\Python\module1.py", line 3, in f
    print a
NameError: global name 'a' is not defined

Domande simili sono state poste in passato (due giorni fa, d'uh) e sono state suggerite diverse soluzioni, tuttavia non credo che queste soddisfino le mie esigenze. Ecco il mio contesto particolare:

Sto cercando di creare un programma Python che si connette a un server di database MySQL e visualizza / modifica i dati con una GUI. Per motivi di pulizia, ho definito il gruppo di funzioni ausiliarie / di utilità relative a MySQL in un file separato. Tuttavia hanno tutti una variabile comune, che avevo originariamente definita all'interno del modulo delle utilità, e che è l' oggetto cursore dal modulo MySQLdb. In seguito mi sono reso conto che l' oggetto cursore (che viene utilizzato per comunicare con il server db) dovrebbe essere definito nel modulo principale, in modo che sia il modulo principale che tutto ciò che viene importato in esso possano accedere a quell'oggetto.

Il risultato finale sarebbe qualcosa del genere:

utilities_module.py:

def utility_1(args):
    code which references a variable named "cur"
def utility_n(args):
    etcetera

E il mio modulo principale:

program.py:

import MySQLdb, Tkinter
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

E poi, non appena provo a chiamare una qualsiasi delle funzioni di utilità, si attiva il suddetto errore "nome globale non definito".

Un suggerimento particolare era di avere un'istruzione "from program import cur" nel file delle utilità, come questa:

utilities_module.py:

from program import cur
#rest of function definitions

program.py:

import Tkinter, MySQLdb
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

Ma questa è importazione ciclica o qualcosa del genere e, in ultima analisi, si blocca anche. Quindi la mia domanda è:

Come diavolo posso rendere l'oggetto "cur", definito nel modulo principale, visibile a quelle funzioni ausiliarie che vengono importate in esso?

Grazie per il tuo tempo e le mie più sentite scuse se la soluzione è stata pubblicata altrove. Non riesco a trovare la risposta da solo e non ho più trucchi nel mio libro.


1
In base al tuo aggiornamento: probabilmente non vuoi comunque un singolo cursore condiviso. Una singola connessione condivisa , sì, ma i cursori sono economici e spesso ci sono buone ragioni per avere più cursori attivi contemporaneamente (ad esempio, quindi puoi iterare attraverso due di essi in blocco invece di doverlo fetch_allscorrere su due elenchi invece , o semplicemente così puoi avere due diversi thread / greenlets / callback-chain / qualunque cosa usi il database senza conflitti).
abarnert

In ogni caso, qualunque cosa si desidera condividere, penso che la risposta qui è quello di spostare db(e cur, se insistete) in un modulo separato che sia programe utilities_moduleimportarlo da. In questo modo non si ottengono dipendenze circolari (importando programmi da moduli che importano programmi) e la confusione che ne deriva.
abarnert

Risposte:


244

I globali in Python sono globali per un modulo , non per tutti i moduli. (Molte persone sono confuse da questo, perché in, diciamo, C, un globale è lo stesso in tutti i file di implementazione a meno che tu non lo faccia esplicitamente static.)

Esistono diversi modi per risolvere questo problema, a seconda del caso d'uso effettivo.


Prima ancora di intraprendere questa strada, chiediti se questo deve davvero essere globale. Forse vuoi davvero una classe, con fcome metodo di istanza, piuttosto che solo una funzione libera? Quindi potresti fare qualcosa del genere:

import module1
thingy1 = module1.Thingy(a=3)
thingy1.f()

Se vuoi davvero un globale, ma è lì solo per essere usato module1, impostalo in quel modulo.

import module1
module1.a=3
module1.f()

D'altra parte, se aè condiviso da un sacco di moduli, mettilo da qualche altra parte e chiedi a tutti di importarlo:

import shared_stuff
import module1
shared_stuff.a = 3
module1.f()

... e, in module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Non utilizzare fromun'importazione a meno che la variabile non sia intesa come costante. from shared_stuff import acreerebbe una nuova avariabile inizializzata a qualsiasi cosa a shared_stuff.acui si fa riferimento al momento dell'importazione, e questa nuova avariabile non sarebbe influenzata dalle assegnazioni a shared_stuff.a.


Oppure, nel raro caso in cui hai davvero bisogno che sia veramente globale ovunque, come un builtin, aggiungilo al modulo builtin. I dettagli esatti differiscono tra Python 2.x e 3.x. In 3.x, funziona in questo modo:

import builtins
import module1
builtins.a = 3
module1.f()

12
Bello e completo.
kindall

Grazie per la tua risposta. Proverò l'approccio import shared_stuff, anche se non riesco a superare il fatto che le variabili definite nel modulo principale non sono realmente globali - Non c'è alcun modo per rendere un nome veramente accessibile a tutti, da qualsiasi parte del programma ?
Nubarke

1
Avere qualcosa "accessibile a tutti, da qualsiasi programma" va decisamente contro lo zen di python , in particolare "Explicit è meglio di implicit". Python ha un design oo molto ben pensato e, se lo usi bene, potresti riuscire a portare avanti il ​​resto della tua carriera in Python senza dover usare di nuovo la parola chiave globale.
Bi Rico

1
AGGIORNAMENTO: GRAZIE! L'approccio shared_stuff ha funzionato bene, con questo penso di poter aggirare qualsiasi altro problema.
Nubarke

1
@DanielArmengod: ho aggiunto la risposta incorporata, nel caso ne avessi davvero bisogno. (E se hai bisogno di maggiori dettagli, cerca SO; ci sono almeno due domande su come aggiungere correttamente cose ai builtin.) Ma, come dice Bi Rico, quasi certamente non ne hai davvero bisogno, o lo vuoi.
abarnert

9

Come soluzione alternativa, potresti considerare l'impostazione delle variabili di ambiente nel livello esterno, in questo modo.

main.py:

import os
os.environ['MYVAL'] = str(myintvariable)

mymodule.py:

import os

myval = None
if 'MYVAL' in os.environ:
    myval = os.environ['MYVAL']

Come precauzione aggiuntiva, gestire il caso in cui MYVAL non è definito all'interno del modulo.


5

Una funzione utilizza le variabili globali del modulo in cui è definita . Invece di impostare a = 3, ad esempio, dovresti impostare module1.a = 3. Quindi, se vuoi essere curdisponibile come globale in utilities_module, imposta utilities_module.cur.

Una soluzione migliore: non utilizzare globali. Passa le variabili necessarie alle funzioni che ne hanno bisogno o crea una classe per raggruppare tutti i dati insieme e passala durante l'inizializzazione dell'istanza.


Invece di scrivere 'import module1', se l'utente avesse scritto 'from module1 import f', allora f sarebbe arrivato nello spazio dei nomi globale di main.py. Ora in main.py se usiamo f (), allora poiché a = 3 e f (definizione di funzione) sono entrambi nello spazio dei nomi globale di main. Questa è una soluzione? Se sbaglio, puoi indirizzarmi a qualsiasi articolo su questo argomento per favore
variabile

Ho usato l'approccio sopra e ho passato le variabili globali come argomenti durante l'istanza delle classi che utilizzano le globali. Questo andava bene, ma qualche tempo dopo abbiamo attivato un controllo del codice Sonarqube del codice e questo si è lamentato del fatto che le funzioni hanno troppi argomenti. Quindi abbiamo dovuto cercare un'altra soluzione. Ora usiamo le variabili d'ambiente che vengono lette da ogni modulo che ne ha bisogno. Non è realmente conforme a OOP, ma è tutto. Funziona solo quando le variabili globali non cambiano durante l'esecuzione del codice.
rimetnac

5

Questo post è solo un'osservazione per il comportamento di Python che ho riscontrato. Forse i consigli che hai letto sopra non funzionano per te se hai fatto la stessa cosa che ho fatto io di seguito.

Vale a dire, ho un modulo che contiene variabili globali / condivise (come suggerito sopra):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Quindi ho avuto il modulo principale che importa le cose condivise con:

import sharedstuff as shared

e alcuni altri moduli che hanno effettivamente popolato questi array. Questi sono chiamati dal modulo principale. Quando esco da questi altri moduli posso vedere chiaramente che gli array sono popolati. Ma quando li ho riletti nel modulo principale, erano vuoti. Questo è stato piuttosto strano per me (beh, sono nuovo in Python). Tuttavia, quando cambio la modalità di importazione di sharedstuff.py nel modulo principale in:

from globals import *

ha funzionato (gli array sono stati popolati).

Sto solo dicendo


2

La soluzione più semplice a questo particolare problema sarebbe stata aggiungere un'altra funzione all'interno del modulo che avrebbe memorizzato il cursore in una variabile globale al modulo. Quindi anche tutte le altre funzioni potrebbero usarlo.

modulo 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

def method(some, args):
    global cursor
    do_stuff(cursor, some, args)

programma principale:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()

2

Poiché le variabili globali sono specifiche del modulo, è possibile aggiungere la seguente funzione a tutti i moduli importati e quindi utilizzarla per:

  • Aggiungi variabili singolari (in formato dizionario) come variabili globali per quelle
  • Trasferisci i tuoi moduli globali principali su di esso.

addglobals = lambda x: globals (). update (x)

Quindi tutto ciò di cui hai bisogno per trasmettere le attuali globali è:

modulo di importazione

module.addglobals (globali ())


1

Dal momento che non l'ho visto nelle risposte sopra, ho pensato di aggiungere la mia semplice soluzione alternativa, che è semplicemente aggiungere un global_dictargomento alla funzione che richiede le variabili globali del modulo chiamante, e quindi passare il dict nella funzione durante la chiamata; per esempio:

# external_module
def imported_function(global_dict=None):
    print(global_dict["a"])


# calling_module
a = 12
from external_module import imported_function
imported_function(global_dict=globals())

>>> 12

0

Il modo OOP per farlo sarebbe rendere il modulo una classe invece di un insieme di metodi non associati. Quindi è possibile utilizzare __init__o un metodo setter per impostare le variabili dal chiamante da utilizzare nei metodi del modulo.

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.