Widoczność zmiennych globalnych w importowanych modułach

113

Napotkałem trochę ściany importującej moduły w skrypcie Pythona. Zrobię co w mojej mocy, aby opisać błąd, dlaczego na niego wpadam i dlaczego wiążę to szczególne podejście, aby rozwiązać mój problem (który opiszę za chwilę):

Załóżmy, że mam moduł, w którym zdefiniowałem kilka funkcji / klas narzędzi, które odnoszą się do jednostek zdefiniowanych w przestrzeni nazw, do której ten moduł pomocniczy zostanie zaimportowany (niech „a” będzie taką jednostką):

moduł 1:

def f():
    print a

Następnie mam program główny, w którym zdefiniowano „a”, do którego chcę zaimportować te narzędzia:

import module1
a=3
module1.f()

Uruchomienie programu wywoła następujący błąd:

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

Podobne pytania zadawano już w przeszłości (dwa dni temu, d'uh) i zasugerowano kilka rozwiązań, jednak nie sądzę, aby pasowały one do moich wymagań. Oto mój szczególny kontekst:

Próbuję stworzyć program w języku Python, który łączy się z serwerem bazy danych MySQL i wyświetla / modyfikuje dane za pomocą GUI. Ze względu na czystość zdefiniowałem kilka pomocniczych / narzędziowych funkcji związanych z MySQL w osobnym pliku. Jednak wszystkie mają wspólną zmienną, którą pierwotnie zdefiniowałem w module narzędzi, a która jest obiektem kursora z modułu MySQLdb. Później zdałem sobie sprawę, że obiekt kursora (który jest używany do komunikacji z serwerem db) powinien być zdefiniowany w module głównym, tak aby zarówno moduł główny, jak i wszystko, co jest do niego importowane, miało dostęp do tego obiektu.

Wynik końcowy wyglądałby mniej więcej tak:

utilities_module.py:

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

I mój główny moduł:

program.py:

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

A potem, gdy tylko spróbuję wywołać którąkolwiek z funkcji narzędziowych, wyzwala wspomniany wyżej błąd „nie zdefiniowano nazwy globalnej”.

Szczególną sugestią było umieszczenie w pliku narzędzi instrukcji „from program import import cur”, na przykład:

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 *

Ale to cykliczny import lub coś w tym rodzaju i, podsumowanie, też się zawiesza. Więc moje pytanie brzmi:

Jak u diabła mogę sprawić, by obiekt "cur", zdefiniowany w głównym module, był widoczny dla tych funkcji pomocniczych, które są do niego importowane?

Dziękuję za poświęcony czas i najgłębsze przeprosiny, jeśli rozwiązanie zostało opublikowane w innym miejscu. Po prostu nie mogę sam znaleźć odpowiedzi i nie mam więcej sztuczek w mojej książce.

Nubarke
źródło
1
Na podstawie Twojej aktualizacji: prawdopodobnie i tak nie chcesz mieć jednego współdzielonego kursora. Pojedyncze wspólne połączenie , tak, ale kursory są tanie i często istnieją dobre powody, aby mieć jednocześnie wiele kursorów (np. Aby można było iterować przez dwa z nich w trybie lockstep, zamiast fetch_allprzechodzić przez dwie listy) , lub po prostu możesz mieć dwa różne wątki / greenlety / łańcuchy wywołań zwrotnych / cokolwiek korzystającego z bazy danych bez konfliktów).
abarnert
W każdym razie, co chcesz udostępnić, myślę, że odpowiedź jest tu poruszać db(i cur, jeśli nalegasz) w osobnym module, że zarówno programi utilities_modulezaimportować go z. W ten sposób nie otrzymujesz zależności cyklicznych (importowanie programu z modułów, które program importuje) i zamieszania, które się z nimi wiążą.
abarnert

Odpowiedzi:

245

Globale w Pythonie są globalne dla modułu , a nie dla wszystkich modułów. (Wiele osób jest tym zdezorientowanych, ponieważ, powiedzmy, C, globalny jest taki sam we wszystkich plikach implementacji, chyba że wyraźnie to zrobisz static.)

Istnieją różne sposoby rozwiązania tego problemu, w zależności od rzeczywistego przypadku użycia.


Zanim jeszcze pójdziesz tą ścieżką, zadaj sobie pytanie, czy to naprawdę musi być globalne. Może naprawdę potrzebujesz klasy z fmetodą instancji, a nie tylko darmowej funkcji? Wtedy możesz zrobić coś takiego:

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

Jeśli naprawdę chcesz globalnego, ale jest tylko po to, by go używać module1, ustaw go w tym module.

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

Z drugiej strony, jeśli ajest współużytkowany przez wiele modułów, umieść go w innym miejscu i niech wszyscy go zaimportują:

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

… Oraz w module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Nie używaj fromimportu, chyba że zmienna ma być stałą. from shared_stuff import autworzy nową azmienną zainicjowaną do tego, do czego shared_stuff.aodwoływano się w czasie importu, a aprzypisania do nie będą miały wpływu na tę nową zmienną shared_stuff.a.


Lub, w rzadkich przypadkach, gdy naprawdę potrzebujesz, aby był naprawdę globalny wszędzie, jak wbudowany, dodaj go do wbudowanego modułu. Dokładne szczegóły różnią się między Pythonem 2.xi 3.x. W 3.x działa to tak:

import builtins
import module1
builtins.a = 3
module1.f()
abarnert
źródło
12
Ładnie i kompleksowo.
kindall
Dziękuję za odpowiedź. Spróbuję podejść do importu shared_stuff, chociaż nie mogę zapomnieć o tym, że zmienne zdefiniowane w głównym module nie są tak naprawdę globalne - czy nie ma sposobu, aby nazwa była naprawdę dostępna dla wszystkich, z dowolnego miejsca w programie ?
Nubarke
1
Posiadanie czegoś „dostępnego dla każdego, z dowolnego programu” jest bardzo sprzeczne z zenem Pythona , a konkretnie „jawne jest lepsze niż ukryte”. Python ma bardzo dobrze przemyślany projekt oo i jeśli będziesz go dobrze używać, możesz udać się do końca swojej kariery w Pythonie bez konieczności ponownego używania słowa kluczowego global.
Bi Rico
1
AKTUALIZACJA: DZIĘKI! Podejście shared_stuff działało dobrze, myślę, że dzięki temu mogę obejść inne problemy.
Nubarke
1
@DanielArmengod: Dodałem wbudowaną odpowiedź, na wypadek gdybyś naprawdę jej potrzebował. (A jeśli potrzebujesz więcej szczegółów, wyszukaj SO; są co najmniej dwa pytania dotyczące prawidłowego dodawania rzeczy do wbudowanych elementów). Ale, jak mówi Bi Rico, prawie na pewno nie potrzebujesz tego ani nie chcesz.
abarnert
9

Aby obejść ten problem, możesz rozważyć ustawienie zmiennych środowiskowych w warstwie zewnętrznej, w ten sposób.

main.py:

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

mymodule.py:

import os

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

Jako dodatkowe zabezpieczenie, zajmij się przypadkiem, gdy MYVAL nie jest zdefiniowany w module.

Vishal
źródło
5

Funkcja korzysta z wartości globalnych modułu, w którym jest zdefiniowana . Zamiast ustawiać a = 3, na przykład, powinieneś ustawić module1.a = 3. Jeśli więc chcesz, aby były curdostępne jako globalne utilities_module, ustaw utilities_module.cur.

Lepsze rozwiązanie: nie używaj globali. Przekaż potrzebne zmienne do funkcji, które ich potrzebują, lub utwórz klasę, aby spakować wszystkie dane razem i przekaż ją podczas inicjowania instancji.

kindall
źródło
Zamiast pisać „import module1”, gdyby użytkownik napisał „from module1 import f”, to f pojawiłoby się w globalnej przestrzeni nazw main.py. Teraz w main.py, jeśli użyjemy f (), to ponieważ a = 3 i f (definicja funkcji) znajdują się w globalnej przestrzeni nazw main. Czy to jest rozwiązanie? Jeśli się mylę, to proszę o skierowanie mnie do dowolnego artykułu na ten temat
zmienna
Użyłem powyższego podejścia i przekazałem wartości globalne jako argumenty podczas tworzenia wystąpienia klas, które używają zmiennych globalnych. To było w porządku, ale jakiś czas później aktywowaliśmy sprawdzanie kodu przez Sonarqube, a to narzekało, że funkcje mają zbyt wiele argumentów. Musieliśmy więc poszukać innego rozwiązania. Teraz używamy zmiennych środowiskowych, które są odczytywane przez każdy moduł, który ich potrzebuje. To nie jest zgodne z OOP, ale to wszystko. Działa to tylko wtedy, gdy wartości globalne nie zmieniają się podczas wykonywania kodu.
rimetnac
5

Ten post jest tylko obserwacją zachowania Pythona, z którym się spotkałem. Może porady, które przeczytałeś powyżej, nie działają dla Ciebie, jeśli zrobiłeś to samo, co poniżej.

Mianowicie mam moduł, który zawiera zmienne globalne / współdzielone (jak zasugerowano powyżej):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Następnie miałem główny moduł, który importuje udostępnione rzeczy z:

import sharedstuff as shared

i kilka innych modułów, które faktycznie zapełniły te tablice. Są one wywoływane przez główny moduł. Wychodząc z tych innych modułów, wyraźnie widzę, że tablice są zapełnione. Ale kiedy czytałem je z powrotem w głównym module, były puste. To było dla mnie dość dziwne (no cóż, jestem nowy w Pythonie). Jednak gdy zmienię sposób importu sharedstuff.py w głównym module na:

from globals import *

zadziałało (tablice zostały wypełnione).

Tylko mówię'

user3336548
źródło
2

Najłatwiejszym rozwiązaniem tego konkretnego problemu byłoby dodanie innej funkcji w module, która przechowywałaby kursor w zmiennej globalnej modułu. Wtedy wszystkie inne funkcje również mogłyby go używać.

moduł 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

główny program:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()
Chris Nasr
źródło
2

Ponieważ wartości globalne są specyficzne dla modułu, możesz dodać następującą funkcję do wszystkich zaimportowanych modułów, a następnie użyć jej do:

  • Dodaj pojedyncze zmienne (w formacie słownikowym) jako globalne dla tych
  • Przenieś do niego swój główny moduł globalny.

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

W takim razie wszystko, co musisz przekazać bieżącym globalom, to:

moduł importu

module.addglobals (globals ())

user2589273
źródło
1

Ponieważ nie widziałem tego w powyższych odpowiedziach, pomyślałem, że dodam moje proste obejście, którym jest po prostu dodanie global_dictargumentu do funkcji wymagającej wartości globalnych modułu wywołującego, a następnie przekazanie dyktu do funkcji podczas wywoływania; na przykład:

# 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
źródło
0

Sposobem OOP na zrobienie tego byłoby uczynienie modułu klasą zamiast zestawu niezwiązanych metod. Następnie możesz użyć __init__metody lub metody ustawiającej, aby ustawić zmienne z obiektu wywołującego do użycia w metodach modułu.

kojot
źródło