Visibilidad de variables globales en módulos importados

112

Me encontré con un poco de un muro al importar módulos en un script de Python. Haré todo lo posible para describir el error, por qué me encuentro con él y por qué estoy vinculando este enfoque en particular para resolver mi problema (que describiré en un segundo):

Supongamos que tengo un módulo en el que he definido algunas funciones / clases de utilidad, que se refieren a entidades definidas en el espacio de nombres en el que se importará este módulo auxiliar (sea "a" una entidad de este tipo):

módulo 1:

def f():
    print a

Y luego tengo el programa principal, donde se define "a", en el que quiero importar esas utilidades:

import module1
a=3
module1.f()

La ejecución del programa provocará el siguiente error:

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

Se han hecho preguntas similares en el pasado (hace dos días, uh) y se han sugerido varias soluciones, sin embargo, no creo que se ajusten a mis requisitos. Aquí está mi contexto particular:

Estoy tratando de crear un programa Python que se conecte a un servidor de base de datos MySQL y muestre / modifique datos con una GUI. En aras de la limpieza, he definido el grupo de funciones auxiliares / utilitarias relacionadas con MySQL en un archivo separado. Sin embargo, todos tienen una variable común, que había definido originalmente dentro del módulo de utilidades, y que es el objeto cursor del módulo MySQLdb. Más tarde me di cuenta de que el objeto cursor (que se usa para comunicarse con el servidor de base de datos) debe definirse en el módulo principal, de modo que tanto el módulo principal como cualquier cosa que se importe en él puedan acceder a ese objeto.

El resultado final sería algo como esto:

utilities_module.py:

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

Y mi módulo principal:

program.py:

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

Y luego, tan pronto como intento llamar a cualquiera de las funciones de utilidades, se activa el error "nombre global no definido" mencionado anteriormente.

Una sugerencia particular fue tener una declaración "from program import cur" en el archivo de utilidades, como esta:

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 *

Pero eso es una importación cíclica o algo así y, en resumen, también falla. Entonces mi pregunta es:

¿Cómo diablos puedo hacer que el objeto "cur", definido en el módulo principal, sea visible para las funciones auxiliares que se importan en él?

Gracias por su tiempo y mis más sinceras disculpas si la solución se ha publicado en otro lugar. No puedo encontrar la respuesta por mí mismo y no tengo más trucos en mi libro.

Nubarke
fuente
1
Según su actualización: probablemente no desee un solo cursor compartido de todos modos. Una sola conexión compartida , sí, pero los cursores son baratos, y a menudo hay buenas razones para tener varios cursores activos al mismo tiempo (por ejemplo, para que pueda iterar a través de dos de ellos en sincronía en lugar de tener que hacerlo fetch_alle iterar a través de dos listas en su lugar , o simplemente para que pueda tener dos hilos / greenlets / callback-chains / lo que sea usando la base de datos sin conflictos).
abarnert
De todos modos, lo que usted desee compartir, creo que la respuesta aquí es mover db(y cur, si insiste) en un módulo independiente que tanto programy utilities_moduleimportarlo de. De esa manera, no obtendrá dependencias circulares (programa de importación de módulos que el programa importa) y la confusión que conlleva.
abarnert

Respuestas:

244

Los globales en Python son globales para un módulo , no para todos los módulos. (Mucha gente está confundida por esto, porque en, digamos, C, un global es el mismo en todos los archivos de implementación a menos que lo establezca explícitamente static).

Hay diferentes formas de resolver esto, dependiendo de su caso de uso real.


Antes incluso de seguir este camino, pregúntese si esto realmente necesita ser global. ¿Quizás realmente desea una clase, fcomo método de instancia, en lugar de solo una función gratuita? Entonces podrías hacer algo como esto:

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

Si realmente desea un global, pero está ahí para ser utilizado module1, configúrelo en ese módulo.

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

Por otro lado, si alo comparten muchos módulos, colóquelo en otro lugar y haga que todos lo importen:

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

… Y, en module1.py:

import shared_stuff
def f():
    print shared_stuff.a

No utilice una fromimportación a menos que la variable esté destinada a ser una constante. from shared_stuff import acrearía una nueva avariable inicializada a lo que sea shared_stuff.areferido en el momento de la importación, y esta nueva avariable no se vería afectada por asignaciones a shared_stuff.a.


O, en el raro caso de que realmente necesite que sea verdaderamente global en todas partes, como un incorporado, agréguelo al módulo incorporado. Los detalles exactos difieren entre Python 2.xy 3.x. En 3.x, funciona así:

import builtins
import module1
builtins.a = 3
module1.f()
abarnert
fuente
12
Agradable y completo.
poco todo el
Gracias por tu respuesta. Probaré el enfoque import shared_stuff, aunque no puedo olvidar el hecho de que las variables definidas en el módulo principal no son realmente globales: ¿no hay alguna forma de hacer que un nombre sea realmente accesible para todos, desde cualquier lugar del programa? ?
Nubarke
1
Tener algo "accesible para todos, desde cualquier programa" va en gran medida en contra del zen de Python , específicamente "Explícito es mejor que implícito". Python tiene un diseño oo muy bien pensado, y si lo usas bien, podrías lograr continuar el resto de tu carrera en Python sin tener que volver a usar la palabra clave global.
Bi Rico
1
ACTUALIZACIÓN: ¡GRACIAS! El enfoque shared_stuff funcionó bien, con esto creo que puedo solucionar cualquier otro problema.
Nubarke
1
@DanielArmengod: agregué la respuesta incorporada, en caso de que realmente la necesite. (Y si necesita más detalles, busque SO; hay al menos dos preguntas sobre cómo agregar cosas a las incorporaciones correctamente). Pero, como dice Bi Rico, es casi seguro que no lo necesita ni lo desea.
abarnert
9

Como solución alternativa, podría considerar establecer variables de entorno en la capa exterior, como esta.

main.py:

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

mymodule.py:

import os

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

Como precaución adicional, maneje el caso cuando MYVAL no esté definido dentro del módulo.

Vishal
fuente
5

Una función usa los globales del módulo en el que está definida . En lugar de establecer a = 3, por ejemplo, debería establecer module1.a = 3. Por lo tanto, si desea que curesté disponible como entrada global utilities_module, configureutilities_module.cur .

Una mejor solución: no use globales. Pase las variables que necesita a las funciones que lo necesiten, o cree una clase para agrupar todos los datos y páselos al inicializar la instancia.

un poco
fuente
En lugar de escribir 'import module1', si el usuario hubiera escrito 'from module1 import f', entonces f vendría en el espacio de nombres global de main.py. Ahora en main.py si usamos f (), entonces, dado que a = 3 y f (definición de función) están en el espacio de nombres global de main. ¿Es esta una solución? Si me equivoco, por favor, ¿puede dirigirme a algún artículo sobre este tema
Variable
Usé el enfoque anterior y pasé los globales como argumentos al crear instancias de las clases que usan los globales. Esto estuvo bien, pero algún tiempo después hemos activado una verificación de código de Sonarqube y esto se quejó de que las funciones tienen demasiados argumentos. Entonces tuvimos que buscar otra solución. Ahora usamos variables de entorno que son leídas por cada módulo que las necesita. No es realmente compatible con OOP, pero eso es todo. Esto solo funciona cuando los globales no cambian durante la ejecución del código.
rimetnac
5

Esta publicación es solo una observación del comportamiento de Python que encontré. Quizás los consejos que leíste arriba no te sirvan si hiciste lo mismo que yo hice a continuación.

Es decir, tengo un módulo que contiene variables globales / compartidas (como se sugirió anteriormente):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Luego tuve el módulo principal que importa las cosas compartidas con:

import sharedstuff as shared

y algunos otros módulos que realmente poblaron estos arreglos. Estos son llamados por el módulo principal. Al salir de estos otros módulos, puedo ver claramente que las matrices están pobladas. Pero al volver a leerlos en el módulo principal, estaban vacíos. Esto fue bastante extraño para mí (bueno, soy nuevo en Python). Sin embargo, cuando cambio la forma en que importo sharedstuff.py en el módulo principal a:

from globals import *

funcionó (las matrices se poblaron).

Sólo digo'

user3336548
fuente
2

La solución más fácil a este problema en particular habría sido agregar otra función dentro del módulo que hubiera almacenado el cursor en una variable global del módulo. Entonces todas las demás funciones también podrían usarlo.

módulo 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

programa principal:

import module1

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

Dado que los globales son específicos del módulo, puede agregar la siguiente función a todos los módulos importados y luego usarla para:

  • Agregue variables singulares (en formato de diccionario) como globales para aquellos
  • Transfiera sus globales del módulo principal a él.

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

Entonces, todo lo que necesita para transmitir los globales actuales es:

módulo de importación

module.addglobals (globals ())

usuario2589273
fuente
1

Como no lo he visto en las respuestas anteriores, pensé en agregar mi solución alternativa simple, que es solo agregar un global_dictargumento a la función que requiere los globales del módulo de llamada, y luego pasar el dict a la función al llamar; p.ej:

# 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
fuente
0

La forma de OOP de hacer esto sería hacer de su módulo una clase en lugar de un conjunto de métodos independientes. Luego, puede usar __init__o un método de establecimiento para establecer las variables del llamador para usar en los métodos del módulo.

coyot
fuente