it-swarm.com.de

Wie erstelle ich modulweite Variablen in Python?

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.

192
daveslab

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.

220
steveha

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.

60
timmwagener

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)
24
Brad Dre

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__
6
Chinmay Kanchi