Come creare una variabile multi-modulo?


122

La __debug__variabile è utile in parte perché influenza ogni modulo. Se voglio creare un'altra variabile che funzioni allo stesso modo, come dovrei farlo?

La variabile (siamo originali e chiamiamola 'foo') non deve essere veramente globale, nel senso che se cambio foo in un modulo, viene aggiornato in altri. Andrebbe bene se potessi impostare foo prima di importare altri moduli e quindi vedrebbero lo stesso valore per esso.

Risposte:


114

Non approvo questa soluzione in alcun modo, forma o forma. Ma se aggiungi una variabile al __builtin__modulo, sarà accessibile come se fosse una globale da qualsiasi altro modulo che include __builtin__- che sono tutte, per impostazione predefinita.

a.py contiene

print foo

b.py contiene

import __builtin__
__builtin__.foo = 1
import a

Il risultato è che viene stampato "1".

Modifica: il __builtin__modulo è disponibile come simbolo locale __builtins__: questo è il motivo della discrepanza tra due di queste risposte. Nota anche che __builtin__è stato rinominato builtinsin python3.


2
Qualche motivo per cui non ti piace questa situazione?
Software entusiasta il

31
Per prima cosa, rompe le aspettative delle persone quando leggono il codice. "Cos'è questo simbolo 'foo' usato qui? Perché non riesco a vedere dove è definito?"
Curt Hagenlocher

9
È anche suscettibile di provocare il caos se una futura versione di Python inizia a utilizzare il nome che hai scelto come un vero e proprio builtin.
intuito il

4
Questa è una buona soluzione per cose come la condivisione di una connessione db con moduli importati. Come controllo di integrità, mi assicuro che il modulo importato asserisca hasattr(__builtin__, "foo").
Mike Ellis

4
Per chiunque legga questa risposta: NON! FARE ! QUESTO ! Davvero, non farlo.
bruno desthuilliers

161

Se hai bisogno di una variabile globale tra moduli, forse sarà sufficiente una semplice variabile globale a livello di modulo.

a.py:

var = 1

b.py:

import a
print a.var
import c
print a.var

c.py:

import a
a.var = 2

Test:

$ python b.py
# -> 1 2

Esempio del mondo reale: global_settings.py di Django (sebbene nelle impostazioni delle app Django vengano utilizzate importando l' oggetto django.conf.settings ).


3
Meglio perché evita possibili conflitti di spazio dei nomi
bgw

Cosa succede se il modulo che stai importando, in questo caso a.py, lo contiene main()? Importa?
sedeh

4
@sedeh: no. Se a.py viene eseguito anche come script, utilizzare if __name__=="__main__"guard in esso per evitare di eseguire codice imprevisto durante l'importazione.
jfs

6
Nel mondo reale, devi stare un po 'attento con questa soluzione. Se un programmatore prende la tua variabile 'globale' usando 'da una import var', (prova questa variazione in c.py) ottiene una copia della variabile al momento dell'importazione.
Paul Whipp

1
@PaulWhipp: sbagliato (suggerimento: usare id()per controllare l'identità)
jfs

25

Credo che ci siano molte circostanze in cui ha senso e semplifica la programmazione avere alcune variabili globali che sono note in diversi moduli (strettamente accoppiati). In questo spirito, vorrei elaborare un po 'l'idea di avere un modulo di globali che viene importato da quei moduli che hanno bisogno di referenziarli.

Quando esiste un solo modulo di questo tipo, lo chiamo "g". In esso, assegno i valori predefiniti per ogni variabile che intendo trattare come globale. In ogni modulo che utilizza uno qualsiasi di essi, non uso "from g import var", poiché questo risulta solo in una variabile locale che viene inizializzata da g solo al momento dell'importazione. Faccio la maggior parte dei riferimenti nella forma g.var e "g". serve come promemoria costante che ho a che fare con una variabile potenzialmente accessibile ad altri moduli.

Se il valore di una tale variabile globale deve essere usato frequentemente in qualche funzione in un modulo, allora quella funzione può fare una copia locale: var = g.var. Tuttavia, è importante rendersi conto che le assegnazioni a var sono locali e g.var globale non può essere aggiornato senza fare riferimento esplicitamente a g.var in un'assegnazione.

Nota che puoi anche avere più moduli globali di questo tipo condivisi da diversi sottoinsiemi dei tuoi moduli per mantenere le cose un po 'più strettamente controllate. Il motivo per cui uso nomi brevi per i miei moduli globali è per evitare di ingombrare troppo il codice con le loro occorrenze. Con solo un po 'di esperienza, diventano abbastanza mnemonici con solo 1 o 2 caratteri.

È ancora possibile assegnare un incarico a, diciamo, gx quando x non era già definito in g, e un modulo diverso può quindi accedere a gx Tuttavia, anche se l'interprete lo consente, questo approccio non è così trasparente ed evito esso. C'è ancora la possibilità di creare accidentalmente una nuova variabile in g come risultato di un errore di battitura nel nome della variabile per un compito. A volte un esame di dir (g) è utile per scoprire eventuali nomi a sorpresa che possono essere sorti da tale incidente.


7
Questa interessante osservazione ha risolto il mio problema: "Non uso" from g import var ", poiché si ottiene solo una variabile locale che viene inizializzata da g solo al momento dell'importazione." Sembra ragionevole presumere che "from..import" sia uguale a "import" ma questo non è vero.
Curtis Yallop

24

Definisci un modulo (chiamalo "globalbaz") e definisci le variabili al suo interno. Tutti i moduli che utilizzano questo "pseudoglobal" dovrebbero importare il modulo "globalbaz" e fare riferimento ad esso utilizzando "globalbaz.var_name"

Funziona indipendentemente dal luogo della modifica, è possibile modificare la variabile prima o dopo l'importazione. Il modulo importato utilizzerà il valore più recente. (L'ho testato in un esempio di giocattolo)

Per chiarimenti, globalbaz.py ha questo aspetto:

var_name = "my_useful_string"

9

Puoi passare le globali di un modulo a un altro:

Nel Modulo A:

import module_b
my_var=2
module_b.do_something_with_my_globals(globals())
print my_var

Nel modulo B:

def do_something_with_my_globals(glob): # glob is simply a dict.
    glob["my_var"]=3

7

Le variabili globali di solito sono una cattiva idea, ma puoi farlo assegnando a __builtins__:

__builtins__.foo = 'something'
print foo

Inoltre, i moduli stessi sono variabili a cui è possibile accedere da qualsiasi modulo. Quindi, se definisci un modulo chiamato my_globals.py:

# my_globals.py
foo = 'something'

Quindi puoi usarlo anche da qualsiasi luogo:

import my_globals
print my_globals.foo

Usare i moduli piuttosto che modificare __builtins__è generalmente un modo più pulito per fare globali di questo tipo.


3
__builtins__è una particolarità di CPython, davvero non dovresti usarlo - usalo meglio __builtin__(o builtinsin Python3) come mostra la risposta accettata
Tobias Kienzler

5

Puoi già farlo con le variabili a livello di modulo. I moduli sono gli stessi indipendentemente dal modulo da cui vengono importati. Quindi puoi rendere la variabile una variabile a livello di modulo in qualsiasi modulo abbia senso inserirla e accedervi o assegnarle da altri moduli. Sarebbe meglio chiamare una funzione per impostare il valore della variabile o per renderla una proprietà di qualche oggetto singleton. In questo modo, se hai bisogno di eseguire del codice quando la variabile viene modificata, puoi farlo senza interrompere l'interfaccia esterna del modulo.

Di solito non è un ottimo modo per fare le cose - usare raramente le globali lo è - ma penso che questo sia il modo più pulito per farlo.


3

Volevo pubblicare una risposta che dice che c'è un caso in cui la variabile non verrà trovata.

Le importazioni cicliche possono interrompere il comportamento del modulo.

Per esempio:

first.py

import second
var = 1

second.py

import first
print(first.var)  # will throw an error because the order of execution happens before var gets declared.

main.py

import first

In questo esempio dovrebbe essere ovvio, ma in una grande base di codice, questo può essere davvero fonte di confusione.


1

Suona come modificare lo __builtin__spazio dei nomi. Per farlo:

import __builtin__
__builtin__.foo = 'some-value'

Non usare __builtins__direttamente (nota la "s" aggiuntiva) - apparentemente questo può essere un dizionario o un modulo. Grazie a ΤΖΩΤΖΙΟΥ per averlo sottolineato, è possibile trovare altro qui .

Ora fooè disponibile per l'uso ovunque.

Non consiglio di farlo in generale, ma l'uso di questo dipende dal programmatore.

L'assegnazione deve essere eseguita come sopra, solo l'impostazione foo = 'some-other-value'la imposterà solo nello spazio dei nomi corrente.


1
Ricordo (da comp.lang.python) che dovrebbe essere evitato l' uso di builtins diretti ; invece, importa il builtin e usalo , come suggerito da Curt Hagenlocher.
tzot

1

Lo uso per un paio di funzioni primitive incorporate che sentivo mancavano davvero. Un esempio è una funzione find che ha la stessa semantica di utilizzo di filter, map, reduce.

def builtin_find(f, x, d=None):
    for i in x:
        if f(i):
            return i
    return d

import __builtin__
__builtin__.find = builtin_find

Una volta eseguito (ad esempio, importando vicino al punto di ingresso) tutti i moduli possono utilizzare find () come se, ovviamente, fosse integrato.

find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.

Nota: puoi farlo, ovviamente, con il filtro e un'altra linea per testare la lunghezza zero, o con la riduzione in una sorta di linea strana, ma ho sempre pensato che fosse strano.


1

Potrei ottenere variabili modificabili (o mutabili ) tra moduli utilizzando un dizionario:

# in myapp.__init__
Timeouts = {} # cross-modules global mutable variables for testing purpose
Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60

# in myapp.mod1
from myapp import Timeouts

def wait_app_up(project_name, port):
    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
    # ...

# in myapp.test.test_mod1
from myapp import Timeouts

def test_wait_app_up_fail(self):
    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
    with self.assertRaises(hlp.TimeoutException) as cm:
        wait_app_up(PROJECT_NAME, PROJECT_PORT)
    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak

All'avvio test_wait_app_up_fail, la durata effettiva del timeout è di 3 secondi.


1

Mi chiedevo se fosse possibile evitare alcuni degli svantaggi dell'utilizzo di variabili globali (vedi ad esempio http://wiki.c2.com/?GlobalVariablesAreBad ) utilizzando uno spazio dei nomi di classe piuttosto che uno spazio dei nomi globale / modulo per passare i valori delle variabili . Il codice seguente indica che i due metodi sono essenzialmente identici. C'è un leggero vantaggio nell'usare gli spazi dei nomi delle classi come spiegato di seguito.

I seguenti frammenti di codice mostrano anche che attributi o variabili possono essere creati ed eliminati dinamicamente sia negli spazi dei nomi globali / modulo che negli spazi dei nomi delle classi.

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

Chiamo questo modulo "wall" poiché viene utilizzato per rimbalzare le variabili. Agirà come uno spazio per definire temporaneamente le variabili globali e gli attributi a livello di classe della classe vuota "router".

source.py

import wall
def sourcefn():
    msg = 'Hello world!'
    wall.msg = msg
    wall.router.msg = msg

Questo modulo importa wall e definisce una singola funzione sourcefnche definisce un messaggio e lo emette tramite due diversi meccanismi, uno tramite globals e uno tramite la funzione router. Notare che le variabili wall.msge wall.router.messagesono definite qui per la prima volta nei rispettivi spazi dei nomi.

dest.py

import wall
def destfn():

    if hasattr(wall, 'msg'):
        print 'global: ' + wall.msg
        del wall.msg
    else:
        print 'global: ' + 'no message'

    if hasattr(wall.router, 'msg'):
        print 'router: ' + wall.router.msg
        del wall.router.msg
    else:
        print 'router: ' + 'no message'

Questo modulo definisce una funzione destfnche utilizza i due diversi meccanismi per ricevere i messaggi emessi dalla sorgente. Consente la possibilità che la variabile "msg" non esista. destfncancella anche le variabili una volta che sono state visualizzate.

main.py

import source, dest

source.sourcefn()

dest.destfn() # variables deleted after this call
dest.destfn()

Questo modulo chiama in sequenza le funzioni definite in precedenza. Dopo la prima chiamata alle dest.destfnvariabili wall.msge wall.router.msgnon esistono più.

L'output del programma è:

globale: Hello world!
router: ciao mondo!
globale: nessun messaggio
router: nessun messaggio

I frammenti di codice sopra mostrano che i meccanismi delle variabili module / global e class / class sono essenzialmente identici.

Se devono essere condivise molte variabili, l'inquinamento dello spazio dei nomi può essere gestito utilizzando diversi moduli di tipo wall, ad esempio wall1, wall2 ecc. O definendo diverse classi di tipo router in un singolo file. Quest'ultimo è leggermente più ordinato, quindi rappresenta forse un vantaggio marginale per l'uso del meccanismo delle variabili di classe.

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.