Sichtbarkeit globaler Variablen in importierten Modulen

111

Ich bin auf eine Art Wand gestoßen, die Module in einem Python-Skript importiert. Ich werde mein Bestes tun, um den Fehler zu beschreiben, warum ich darauf stoße und warum ich diesen speziellen Ansatz zur Lösung meines Problems binde (den ich gleich beschreiben werde):

Angenommen, ich habe ein Modul, in dem ich einige Dienstprogrammfunktionen / -klassen definiert habe, die sich auf Entitäten beziehen, die im Namespace definiert sind, in den dieses Hilfsmodul importiert wird (sei "a" eine solche Entität):

Modul 1:

def f():
    print a

Und dann habe ich das Hauptprogramm, in dem "a" definiert ist, in das ich diese Dienstprogramme importieren möchte:

import module1
a=3
module1.f()

Das Ausführen des Programms löst den folgenden Fehler aus:

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

Ähnliche Fragen wurden in der Vergangenheit gestellt (vor zwei Tagen, d'uh) und es wurden verschiedene Lösungen vorgeschlagen, aber ich denke nicht, dass diese meinen Anforderungen entsprechen. Hier ist mein besonderer Kontext:

Ich versuche ein Python-Programm zu erstellen, das eine Verbindung zu einem MySQL-Datenbankserver herstellt und Daten über eine GUI anzeigt / ändert. Aus Gründen der Sauberkeit habe ich die Reihe der MySQL-bezogenen Hilfs- / Dienstprogrammfunktionen in einer separaten Datei definiert. Jedoch haben sie alle eine gemeinsame Variable, die ich ursprünglich definiert war innerhalb des Versorgungsmoduls und die der Cursor - Objekt aus MySQLdb Modul. Später wurde mir klar, dass das Cursorobjekt (das zur Kommunikation mit dem Datenbankserver verwendet wird) im Hauptmodul definiert werden sollte, damit sowohl das Hauptmodul als auch alles, was in das Modul importiert wird, auf dieses Objekt zugreifen können.

Das Endergebnis wäre ungefähr so:

utilities_module.py:

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

Und mein Hauptmodul:

program.py:

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

Sobald ich versuche, eine der Dienstprogrammfunktionen aufzurufen, wird der oben genannte Fehler "Globaler Name nicht definiert" ausgelöst.

Ein besonderer Vorschlag war, eine Anweisung "from program import cur" in die Dienstprogrammdatei aufzunehmen, wie folgt:

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 *

Aber das ist zyklischer Import oder so ähnlich und unter dem Strich stürzt es auch ab. Meine Frage lautet also:

Wie zum Teufel kann ich das im Hauptmodul definierte "cur" -Objekt für die darin importierten Hilfsfunktionen sichtbar machen?

Vielen Dank für Ihre Zeit und meine tiefste Entschuldigung, wenn die Lösung an anderer Stelle veröffentlicht wurde. Ich kann die Antwort einfach nicht selbst finden und habe keine Tricks mehr in meinem Buch.

Nubarke
quelle
1
Basierend auf Ihrem Update: Sie möchten wahrscheinlich sowieso keinen einzigen freigegebenen Cursor. Eine einzelne gemeinsam genutzte Verbindung , ja, aber Cursor sind billig, und es gibt oft gute Gründe, mehrere Cursor gleichzeitig am Leben zu haben (z. B. können Sie zwei davon im fetch_allGleichschritt durchlaufen, anstatt stattdessen zwei Listen durchlaufen zu müssen oder nur so können Sie zwei verschiedene Threads / Greenlets / Callback-Ketten / was auch immer verwenden, die die Datenbank ohne Konflikte verwenden).
abarnert
Was auch immer Sie teilen möchten, ich denke, die Antwort hier ist, in ein separates Modul zu wechseln db(und cur, wenn Sie darauf bestehen), das beides programund es utilities_moduleimportiert. Auf diese Weise erhalten Sie keine zirkulären Abhängigkeiten (Importieren von Programmen aus Modulen, die vom Programm importiert werden) und die damit verbundene Verwirrung.
abarnert

Antworten:

242

Globale Werte in Python sind für ein Modul global und nicht für alle Module. (Viele Leute sind davon verwirrt, weil beispielsweise in C ein Global in allen Implementierungsdateien gleich ist, es sei denn, Sie machen es explizit static.)

Abhängig von Ihrem tatsächlichen Anwendungsfall gibt es verschiedene Möglichkeiten, dies zu lösen.


Bevor Sie diesen Weg gehen, fragen Sie sich, ob dies wirklich global sein muss. Vielleicht möchten Sie wirklich eine Klasse fals Instanzmethode und nicht nur eine freie Funktion? Dann könnten Sie so etwas tun:

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

Wenn Sie wirklich eine globale Version möchten, diese aber nur verwendet werden soll module1, legen Sie sie in diesem Modul fest.

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

Wenn aes jedoch von vielen Modulen gemeinsam genutzt wird, platzieren Sie es an einem anderen Ort und lassen Sie es von allen importieren:

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

… Und in module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Sie nicht eine Verwendung fromImport es sei denn , die Variable soll eine Konstante sein. from shared_stuff import awürde eine neue aVariable erstellen shared_stuff.a, die auf das initialisiert ist, worauf zum Zeitpunkt des Imports Bezug genommen wurde, und diese neue aVariable wäre von Zuweisungen an nicht betroffen shared_stuff.a.


Oder, in dem seltenen Fall, dass Sie es wirklich brauchen, um überall wirklich global zu sein, wie ein eingebautes, fügen Sie es dem eingebauten Modul hinzu. Die genauen Details unterscheiden sich zwischen Python 2.x und 3.x. In 3.x funktioniert es folgendermaßen:

import builtins
import module1
builtins.a = 3
module1.f()
abarnert
quelle
12
Schön und umfassend.
Kindall
Danke für deine Antwort. Ich werde den Import-Ansatz "shared_stuff" ausprobieren, obwohl ich nicht darüber hinwegkommen kann, dass die im Hauptmodul definierten Variablen nicht wirklich global sind. Gibt es keine Möglichkeit, einen Namen wirklich für alle zugänglich zu machen, egal wo im Programm ?
Nubarke
1
Etwas "für jedermann zugänglich, von welchem ​​Programm auch immer" zu haben, widerspricht sehr dem Zen von Python , insbesondere "Explizit ist besser als implizit". Python hat ein sehr gut durchdachtes Design, und wenn Sie es gut verwenden, können Sie möglicherweise den Rest Ihrer Python-Karriere fortsetzen, ohne jemals wieder das globale Schlüsselwort verwenden zu müssen.
Bi Rico
1
UPDATE: DANKE! Der shared_stuff-Ansatz hat einwandfrei funktioniert. Ich denke, ich kann damit alle anderen Probleme umgehen.
Nubarke
1
@ DanielArmengod: Ich habe die eingebaute Antwort hinzugefügt, falls Sie sie wirklich brauchen. (Und wenn Sie weitere Details benötigen, suchen Sie SO; es gibt mindestens zwei Fragen zum richtigen Hinzufügen von Inhalten zu integrierten Funktionen.) Aber wie Bi Rico sagt, brauchen oder wollen Sie es mit ziemlicher Sicherheit nicht wirklich.
abarnert
8

Um dieses Problem zu umgehen, können Sie Umgebungsvariablen wie folgt in der äußeren Ebene festlegen.

main.py:

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

mymodule.py:

import os

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

Behandeln Sie als zusätzliche Vorsichtsmaßnahme den Fall, wenn MYVAL nicht im Modul definiert ist.

Vishal
quelle
5

Eine Funktion verwendet die Globals des Moduls, in dem sie definiert ist . Anstatt a = 3beispielsweise Einstellungen vorzunehmen , sollten Sie Einstellungen vornehmen module1.a = 3. Wenn Sie also curals globales In verfügbar sein möchten utilities_module, legen Sie fest utilities_module.cur.

Eine bessere Lösung: Verwenden Sie keine Globals. Übergeben Sie die benötigten Variablen an die Funktionen, die sie benötigen, oder erstellen Sie eine Klasse, um alle Daten zu bündeln, und übergeben Sie sie beim Initialisieren der Instanz.

irgendwie
quelle
Anstatt 'import module1' zu schreiben, würde f in den globalen Namespace von main.py kommen, wenn der Benutzer 'from module1 import f' geschrieben hätte. Wenn wir nun in main.py f () verwenden, befinden sich a = 3 und f (Funktionsdefinition) beide im globalen Namespace von main. Ist das eine Lösung? Wenn ich falsch liege, können Sie mich bitte auf einen Artikel zu diesem Thema verweisen
Variable
Ich habe den obigen Ansatz verwendet und die Globals als Argumente übergeben, wenn ich die Klassen instanziiert habe, die die Globals verwenden. Dies war in Ordnung, aber einige Zeit später haben wir eine Sonarqube-Codeprüfung des Codes aktiviert und dies beschwerte sich, dass Funktionen zu viele Argumente haben. Also mussten wir nach einer anderen Lösung suchen. Jetzt verwenden wir Umgebungsvariablen, die von jedem Modul gelesen werden, das sie benötigt. Es ist nicht wirklich OOP-konform, aber das war's. Dies funktioniert nur, wenn sich die Globals während der Codeausführung nicht ändern.
Rimetnac
5

Dieser Beitrag ist nur eine Beobachtung des Python-Verhaltens, auf das ich gestoßen bin. Vielleicht funktionieren die Ratschläge, die Sie oben gelesen haben, nicht für Sie, wenn Sie dasselbe gemacht haben, was ich unten getan habe.

Ich habe nämlich ein Modul, das globale / gemeinsam genutzte Variablen enthält (wie oben vorgeschlagen):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Dann hatte ich das Hauptmodul, das die gemeinsam genutzten Inhalte importiert mit:

import sharedstuff as shared

und einige andere Module, die diese Arrays tatsächlich gefüllt haben. Diese werden vom Hauptmodul aufgerufen. Beim Beenden dieser anderen Module kann ich deutlich sehen, dass die Arrays gefüllt sind. Beim Zurücklesen im Hauptmodul waren sie jedoch leer. Das war ziemlich seltsam für mich (nun, ich bin neu in Python). Wenn ich jedoch die Art und Weise ändere, in der ich die Datei sharedstuff.py im Hauptmodul importiere, in:

from globals import *

es funktionierte (die Arrays wurden gefüllt).

Ich sag bloß'

user3336548
quelle
2

Die einfachste Lösung für dieses spezielle Problem wäre gewesen, eine weitere Funktion innerhalb des Moduls hinzuzufügen, die den Cursor in einer globalen Variablen zum Modul gespeichert hätte. Dann könnten es auch alle anderen Funktionen nutzen.

Modul 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

Hauptprogramm:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()
Chris Nasr
quelle
2

Da Globals modulspezifisch sind, können Sie allen importierten Modulen die folgende Funktion hinzufügen und sie dann verwenden, um:

  • Fügen Sie singuläre Variablen (im Wörterbuchformat) als globale Variablen für diese hinzu
  • Übertragen Sie Ihre Haupt- Modul Globals zu.

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

Dann brauchen Sie nur noch die aktuellen globalen Informationen weiterzugeben:

Modul importieren

module.addglobals (globals ())

user2589273
quelle
1

Da ich es in den obigen Antworten nicht gesehen habe, dachte ich, ich würde meine einfache global_dictProblemumgehung hinzufügen, indem ich der Funktion, die die Globals des aufrufenden Moduls erfordert, nur ein Argument hinzufüge und dann das Diktat beim Aufruf an die Funktion übergebe. z.B:

# 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
Toby Petty
quelle
0

Die OOP-Methode hierfür besteht darin, Ihr Modul zu einer Klasse anstelle einer Reihe ungebundener Methoden zu machen. Dann können Sie __init__oder eine Setter-Methode verwenden, um die Variablen des Aufrufers für die Verwendung in den Modulmethoden festzulegen.

Kojot
quelle