Gibt es eine Möglichkeit, eine globale Variable innerhalb eines Moduls einzurichten? Als ich versuchte, es auf die offensichtlichste Art und Weise zu tun, sagte der Python Interpreter die Variable __DBNAME__
hat nicht existiert.
...
__DBNAME__ = None
def initDB(name):
if not __DBNAME__:
__DBNAME__ = name
else:
raise RuntimeError("Database name has already been set.")
...
Und nach dem Importieren des Moduls in eine andere Datei
...
import mymodule
mymodule.initDB('mydb.sqlite')
...
Und der Traceback war: UnboundLocalError: local variable '__DBNAME__' referenced before assignment
Irgendwelche Ideen? Ich versuche, einen Singleton mithilfe eines Moduls gemäß der Empfehlung des Kollegen einzurichten.
Hier ist was los ist.
Erstens sind die einzigen globalen Variablen, die Python wirklich hat, modulbezogene Variablen. Sie können keine Variable erstellen, die wirklich global ist. Sie können lediglich eine Variable in einem bestimmten Bereich erstellen. (Wenn Sie eine Variable im Python -Interpreter erstellen und dann andere Module importieren, befindet sich Ihre Variable im äußersten Bereich und ist daher in Ihrer Python -Sitzung global.)
Alles, was Sie tun müssen, um eine modulglobale Variable zu erstellen, ist nur einen Namen zuzuweisen.
Stellen Sie sich eine Datei mit dem Namen foo.py vor, die diese einzelne Zeile enthält:
X = 1
Stellen Sie sich jetzt vor, Sie importieren es.
import foo
print(foo.X) # prints 1
Nehmen wir jedoch an, Sie möchten eine Ihrer Modulbereichsvariablen als globales Element in einer Funktion verwenden, wie in Ihrem Beispiel. Bei Python wird standardmäßig davon ausgegangen, dass Funktionsvariablen lokal sind. Sie fügen einfach eine global
-Deklaration in Ihre Funktion ein, bevor Sie versuchen, die globale zu verwenden.
def initDB(name):
global __DBNAME__ # add this line!
if __DBNAME__ is None: # see notes below; explicit test for None
__DBNAME__ = name
else:
raise RuntimeError("Database name has already been set.")
Übrigens ist für dieses Beispiel der einfache Test if not __DBNAME__
Ausreichend, da jeder andere Zeichenfolgenwert als eine leere Zeichenfolge true ergibt, sodass jeder tatsächliche Datenbankname true ergibt. Aber für Variablen, die einen Zahlenwert von 0 enthalten könnten, können Sie nicht einfach if not variablename
Sagen. In diesem Fall sollten Sie explizit mit dem Operator None
auf is
testen. Ich habe das Beispiel geändert, um einen expliziten None
Test hinzuzufügen. Der explizite Test für None
ist nie falsch, daher verwende ich ihn standardmäßig.
Wie andere auf dieser Seite angemerkt haben, signalisieren zwei führende Unterstriche Python, dass die Variable für das Modul "privat" sein soll. Wenn Sie jemals einen import * from mymodule
Durchführen, importiert Python keine Namen mit zwei führenden Unterstrichen in Ihren Namensraum. Wenn Sie jedoch nur ein einfaches import mymodule
Ausführen und dann dir(mymodule)
sagen, werden die "privaten" Variablen in der Liste angezeigt, und wenn Sie explizit auf mymodule.__DBNAME__
Python kümmert sich nicht darum, sondern lässt Sie einfach darauf verweisen. Die doppelt führenden Unterstriche sind ein wichtiger Hinweis für Benutzer Ihres Moduls, dass sie diesen Namen nicht auf einen eigenen Wert zurückführen möchten.
Es wird in Python als bewährte Methode angesehen, import *
Nicht auszuführen, sondern die Kopplung zu minimieren und die Aussagekraft zu maximieren, indem entweder mymodule.something
Oder explizit ein Import wie from mymodule import something
.
BEARBEITEN: Wenn Sie aus irgendeinem Grund in einer sehr alten Version von Python, die das Schlüsselwort global
nicht enthält, so etwas tun müssen, gibt es eine einfache Problemumgehung. Anstatt eine globale Modulvariable direkt festzulegen, verwenden Sie einen veränderlichen Typ auf globaler Modulebene und speichern Sie Ihre Werte darin.
In Ihren Funktionen ist der Name der globalen Variablen schreibgeschützt. Sie können den tatsächlichen globalen Variablennamen nicht erneut binden. (Wenn Sie diesen Variablennamen in Ihrer Funktion zuweisen, wirkt sich dies nur auf den Namen der lokalen Variablen in der Funktion aus.) Sie können diesen lokalen Variablennamen jedoch verwenden, um auf das tatsächliche globale Objekt zuzugreifen und Daten darin zu speichern.
Sie können ein list
verwenden, aber Ihr Code wird hässlich sein:
__DBNAME__ = [None] # use length-1 list as a mutable
# later, in code:
if __DBNAME__[0] is None:
__DBNAME__[0] = name
Ein dict
ist besser. Am bequemsten ist jedoch eine Klasseninstanz, und Sie können einfach eine einfache Klasse verwenden:
class Box:
pass
__m = Box() # m will contain all module-level values
__m.dbname = None # database name global in module
# later, in code:
if __m.dbname is None:
__m.dbname = name
(Sie müssen die Datenbanknamenvariable nicht wirklich groß schreiben.)
Mir gefällt der syntaktische Zucker, wenn ich nur __m.dbname
Und nicht __m["DBNAME"]
Verwende. es scheint meiner Meinung nach die bequemste Lösung zu sein. Aber die dict
-Lösung funktioniert auch einwandfrei.
Mit einem dict
können Sie einen beliebigen Hashwert als Schlüssel verwenden. Wenn Sie jedoch mit Namen zufrieden sind, die gültige Bezeichner sind, können Sie oben eine einfache Klasse wie Box
verwenden.
Expliziter Zugriff auf Variablen auf Modulebene durch Zugriff auf sie im Modul
Kurz gesagt: Die hier beschriebene Technik ist dieselbe wie in Stevehas Antwort , = ausgenommen, dass kein künstliches Hilfsobjekt erstellt wird, um Variablen explizit zu erfassen. Stattdessen erhält das Modulobjekt selbst einen variablen Zeiger und bietet daher einen expliziten Gültigkeitsbereich für den Zugriff von überall. (wie Zuweisungen im lokalen Funktionsumfang).
Stellen Sie es sich wie self für das aktuelle Modul vor anstelle der aktuellen Instanz!
# db.py
import sys
# this is a pointer to the module object instance itself.
this = sys.modules[__name__]
# we can explicitly make assignments on it
this.db_name = None
def initialize_db(name):
if (this.db_name is None):
# also in local function scope. no scope specifier like global is needed
this.db_name = name
# also the name remains free for local use
db_name = "Locally scoped db_name variable. Doesn't do anything here."
else:
msg = "Database is already initialized to {0}."
raise RuntimeError(msg.format(this.db_name))
Da Module zwischengespeichert werden und daher nur einmal importiert werden , können Sie db.py
So oft auf beliebig vielen Clients importieren und dabei denselben universellen Status bearbeiten:
# client_a.py
import db
db.initialize_db('mongo')
# client_b.py
import db
if (db.db_name == 'mongo'):
db.db_name = None # this is the preferred way of usage, as it updates the value for all clients, because they access the same reference from the same module object
# client_c.py
from db import db_name
# be careful when importing like this, as a new reference "db_name" will
# be created in the module namespace of client_c, which points to the value
# that "db.db_name" has at import time of "client_c".
if (db_name == 'mongo'): # checking is fine if "db.db_name" doesn't change
db_name = None # be careful, because this only assigns the reference client_c.db_name to a new value, but leaves db.db_name pointing to its current value.
Als zusätzlichen Bonus finde ich es insgesamt ziemlich pythonisch, da es gut zur Python-Richtlinie von Explizit ist besser als implizit passt.
Stevehas Antwort war hilfreich für mich, lässt aber einen wichtigen Punkt aus (einen, bei dem ich denke, dass Wisty weiterkam). Das globale Schlüsselwort ist nicht erforderlich, wenn Sie die Variable in der Funktion nur aufrufen, aber nicht zuweisen.
Wenn Sie die Variable ohne das globale Schlüsselwort zuweisen, erstellt Python= eine neue lokale Variable - der Wert der Modulvariablen wird jetzt in der Funktion ausgeblendet. Verwenden Sie das globale Schlüsselwort, um die Modulvariable in einem zuzuweisen Funktion.
Pylint 1.3.1 unter Python 2.7 erzwingt NOT using global, wenn Sie die Variable nicht zuweisen.
module_var = '/dev/hello'
def readonly_access():
connect(module_var)
def readwrite_access():
global module_var
module_var = '/dev/hello2'
connect(module_var)
Dazu müssen Sie die Variable als global deklarieren. Auf eine globale Variable kann jedoch auch von außerhalb des Moduls mit module_name.var_name
Zugegriffen werden. Fügen Sie dies als erste Zeile Ihres Moduls hinzu:
global __DBNAME__