Visibilité des variables globales dans les modules importés

112

Je suis tombé sur un mur en important des modules dans un script Python. Je ferai de mon mieux pour décrire l'erreur, pourquoi je la rencontre et pourquoi j'attache cette approche particulière pour résoudre mon problème (que je décrirai dans une seconde):

Supposons que j'ai un module dans lequel j'ai défini des fonctions / classes utilitaires, qui se réfèrent aux entités définies dans l'espace de noms dans lequel ce module auxiliaire sera importé (soit "a" une telle entité):

module 1:

def f():
    print a

Et puis j'ai le programme principal, où "a" est défini, dans lequel je veux importer ces utilitaires:

import module1
a=3
module1.f()

L'exécution du programme déclenchera l'erreur suivante:

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

Des questions similaires ont été posées dans le passé (il y a deux jours, d'uh) et plusieurs solutions ont été suggérées, mais je ne pense pas vraiment qu'elles correspondent à mes besoins. Voici mon contexte particulier:

J'essaie de créer un programme Python qui se connecte à un serveur de base de données MySQL et affiche / modifie les données avec une interface graphique. Par souci de propreté, j'ai défini le tas de fonctions auxiliaires / utilitaires liées à MySQL dans un fichier séparé. Cependant, ils ont tous une variable commune, que j'avais initialement définie dans le module utilitaires, et qui est l' objet curseur du module MySQLdb. J'ai réalisé plus tard que l' objet curseur (qui est utilisé pour communiquer avec le serveur db) devait être défini dans le module principal, de sorte que le module principal et tout ce qui y est importé puisse accéder à cet objet.

Le résultat final serait quelque chose comme ceci:

utilities_module.py:

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

Et mon module principal:

program.py:

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

Et puis, dès que j'essaye d'appeler l'une des fonctions utilitaires, cela déclenche l'erreur "nom global non défini" susmentionnée.

Une suggestion particulière était d'avoir une instruction "from program import cur" dans le fichier utilitaires, comme ceci:

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 *

Mais c'est une importation cyclique ou quelque chose comme ça et, en bout de ligne, ça plante aussi. Ma question est donc:

Comment diable puis-je rendre l'objet "cur", défini dans le module principal, visible par les fonctions auxiliaires qui y sont importées?

Merci pour votre temps et mes plus sincères excuses si la solution a été publiée ailleurs. Je ne trouve tout simplement pas la réponse moi-même et je n'ai plus d'astuces dans mon livre.

Nubarke
la source
1
En fonction de votre mise à jour: vous ne voulez probablement pas d'un seul curseur partagé de toute façon. Une seule connexion partagée , oui, mais les curseurs sont bon marché, et il y a souvent de bonnes raisons d'avoir plusieurs curseurs en vie en même temps (par exemple, pour pouvoir parcourir deux d'entre eux en même temps au lieu d'avoir à fetch_allparcourir deux listes à la place. , ou simplement pour que vous puissiez avoir deux threads / greenlets / callback-chains / tout ce qui utilise la base de données sans conflits).
abarnert
Quoi qu'il en soit, quoi que vous vouliez partager, je pense que la réponse ici est de déplacer db(et cur, si vous insistez) dans un module séparé qui les deux programet de l' utilities_moduleimporter. De cette façon, vous n'obtenez pas de dépendances circulaires (importation de programme à partir de modules importés par le programme) et la confusion qui les accompagne.
abarnert

Réponses:

244

Les globaux en Python sont globaux à un module , pas à tous les modules. (Beaucoup de gens sont déroutés par cela, car dans, par exemple, C, un global est le même dans tous les fichiers d'implémentation, sauf si vous le faites explicitement static.)

Il existe différentes façons de résoudre ce problème, en fonction de votre cas d'utilisation réel.


Avant même de s'engager dans cette voie, demandez-vous si cela doit vraiment être mondial. Peut-être voulez-vous vraiment une classe, avec fcomme méthode d'instance, plutôt qu'une simple fonction gratuite? Ensuite, vous pouvez faire quelque chose comme ceci:

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

Si vous voulez vraiment un global, mais qu'il est juste là pour être utilisé par module1, placez-le dans ce module.

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

D'un autre côté, s'il aest partagé par un grand nombre de modules, placez-le ailleurs et demandez à tout le monde de l'importer:

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

… Et, dans module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Ne pas utiliser une fromimportation à moins que la variable est destinée à être une constante. from shared_stuff import acréerait une nouvelle avariable initialisée à tout ce qui était shared_stuff.aréférencé au moment de l'importation, et cette nouvelle avariable ne serait pas affectée par les affectations à shared_stuff.a.


Ou, dans le cas rare où vous en avez vraiment besoin pour être vraiment global partout, comme un intégré, ajoutez-le au module intégré. Les détails exacts diffèrent entre Python 2.x et 3.x. Dans 3.x, cela fonctionne comme ceci:

import builtins
import module1
builtins.a = 3
module1.f()
Abarnert
la source
12
Agréable et complet.
kindall
Merci pour votre réponse. J'essaierai l'approche import shared_stuff, bien que je ne puisse pas surmonter le fait que les variables définies dans le module principal ne sont pas vraiment globales - N'y a-t-il aucun moyen de rendre un nom vraiment accessible à tout le monde, de n'importe où dans le programme ?
Nubarke
1
Avoir quelque chose "accessible à tout le monde, quel que soit le programme" va à l'encontre du zen de python , en particulier "l'explicite vaut mieux que l'implicite". Python a un design oo très bien pensé, et si vous l'utilisez bien, vous pourriez réussir à faire le reste de votre carrière python sans jamais avoir à utiliser à nouveau le mot-clé global.
Bi Rico
1
MISE À JOUR: MERCI! L'approche shared_stuff a très bien fonctionné, avec cela, je pense que je peux contourner tout autre problème.
Nubarke
1
@DanielArmengod: J'ai ajouté la réponse intégrée, au cas où vous en auriez vraiment besoin. (Et si vous avez besoin de plus de détails, recherchez SO; il y a au moins deux questions sur la façon d'ajouter correctement des éléments aux builtins.) Mais, comme le dit Bi Rico, vous n'en avez presque certainement pas vraiment besoin, ou vous n'en voulez pas.
abarnert
9

Pour contourner ce problème, vous pouvez envisager de définir des variables d'environnement dans la couche externe, comme ceci.

main.py:

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

mymodule.py:

import os

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

Par précaution supplémentaire, traitez le cas où MYVAL n'est pas défini à l'intérieur du module.

Vishal
la source
5

Une fonction utilise les globaux du module dans lequel elle est définie . Au lieu de définir a = 3, par exemple, vous devriez définir module1.a = 3. Donc, si vous voulez être curdisponible en tant que global dans utilities_module, définissezutilities_module.cur .

Une meilleure solution: n'utilisez pas de globaux. Transmettez les variables dont vous avez besoin aux fonctions qui en ont besoin, ou créez une classe pour regrouper toutes les données et transmettez-la lors de l'initialisation de l'instance.

kindall
la source
Au lieu d'écrire 'import module1', si l'utilisateur avait écrit 'from module1 import f', alors f viendrait dans l'espace de noms global de main.py. Maintenant dans main.py si nous utilisons f (), alors puisque a = 3 et f (définition de fonction) sont tous les deux dans l'espace de nom global de main. Est-ce une solution? Si je me trompe, pouvez-vous me diriger vers n'importe quel article sur ce sujet s'il vous plaît
variable
J'ai utilisé l'approche ci-dessus et passé les globals comme arguments lors de l'instanciation des classes qui utilisent les globals. C'était correct, mais quelque temps plus tard, nous avons activé une vérification du code Sonarqube du code et cela s'est plaint que les fonctions ont trop d'arguments. Nous avons donc dû chercher une autre solution. Maintenant, nous utilisons des variables d'environnement qui sont lues par chaque module qui en a besoin. Ce n'est pas vraiment conforme à la POO mais c'est tout. Cela ne fonctionne que lorsque les globaux ne changent pas pendant l'exécution du code.
rimetnac
5

Cet article n'est qu'une observation pour le comportement de Python que j'ai rencontré. Peut-être que les conseils que vous avez lus ci-dessus ne fonctionnent pas pour vous si vous avez fait la même chose que moi ci-dessous.

À savoir, j'ai un module qui contient des variables globales / partagées (comme suggéré ci-dessus):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Ensuite, j'ai eu le module principal qui importe le contenu partagé avec:

import sharedstuff as shared

et quelques autres modules qui ont en fait peuplé ces tableaux. Ceux-ci sont appelés par le module principal. En quittant ces autres modules, je peux clairement voir que les tableaux sont remplis. Mais en les relisant dans le module principal, ils étaient vides. C'était plutôt étrange pour moi (enfin, je suis nouveau sur Python). Cependant, lorsque je change la façon dont j'importe le sharedstuff.py dans le module principal pour:

from globals import *

cela a fonctionné (les tableaux étaient remplis).

Dis juste

user3336548
la source
2

La solution la plus simple à ce problème particulier aurait été d'ajouter une autre fonction au sein du module qui aurait stocké le curseur dans une variable globale du module. Ensuite, toutes les autres fonctions pourraient également l'utiliser.

module 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

programme principal:

import module1

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

Étant donné que les globaux sont spécifiques au module, vous pouvez ajouter la fonction suivante à tous les modules importés, puis l'utiliser pour:

  • Ajouter des variables singulières (au format dictionnaire) comme globales pour celles-ci
  • Transférez-y les globaux de votre module principal .

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

Ensuite, tout ce dont vous avez besoin pour transmettre les globaux actuels est:

module d'importation

module.addglobals (globals ())

user2589273
la source
1

Comme je ne l'ai pas vu dans les réponses ci-dessus, j'ai pensé ajouter ma solution de contournement simple, qui consiste simplement à ajouter un global_dictargument à la fonction nécessitant les globaux du module appelant, puis à passer le dict dans la fonction lors de l'appel; par exemple:

# 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
la source
0

La manière OOP de faire cela serait de faire de votre module une classe au lieu d'un ensemble de méthodes indépendantes. Ensuite, vous pouvez utiliser __init__ou une méthode setter pour définir les variables de l'appelant à utiliser dans les méthodes du module.

coyot
la source