¿Cómo hacer una variable de módulo cruzado?


122

La __debug__variable es útil en parte porque afecta a cada módulo. Si quiero crear otra variable que funcione de la misma manera, ¿cómo lo haría?

La variable (seamos originales y llamémosla 'foo') no tiene que ser verdaderamente global, en el sentido de que si cambio foo en un módulo, se actualiza en otros. Estaría bien si pudiera configurar foo antes de importar otros módulos y luego verían el mismo valor.

Respuestas:


114

No respaldo esta solución de ninguna manera o forma. Pero si agrega una variable al __builtin__módulo, será accesible como si fuera un global desde cualquier otro módulo que incluya __builtin__, que es todos ellos, de forma predeterminada.

a.py contiene

print foo

b.py contiene

import __builtin__
__builtin__.foo = 1
import a

El resultado es que se imprime "1".

Editar: el __builtin__módulo está disponible como el símbolo local __builtins__; esa es la razón de la discrepancia entre dos de estas respuestas. También tenga en cuenta que __builtin__ha sido renombrado builtinsen python3.


2
¿Alguna razón por la que no te gusta esta situación?
Software entusiasta el

31
Por un lado, rompe las expectativas de las personas cuando leen el código. "¿Qué es este símbolo 'foo' que se usa aquí? ¿Por qué no puedo ver dónde está definido?"
Curt Hagenlocher

9
También es susceptible de causar estragos si una versión futura de Python comienza a usar el nombre que eligió como una versión incorporada real.
intuido

44
Esta es una buena solución para cosas como compartir una conexión db con módulos importados. Como verificación de la cordura, me aseguro de que el módulo importado se afirme hasattr(__builtin__, "foo").
Mike Ellis

44
Para cualquiera que lea esta respuesta: ¡NO! Hacer! ESTA ! Realmente no lo hagas.
bruno desthuilliers

161

Si necesita una variable global de módulo cruzado, tal vez solo sea suficiente una variable de nivel de módulo global simple.

a.py:

var = 1

b.py:

import a
print a.var
import c
print a.var

c.py:

import a
a.var = 2

Prueba:

$ python b.py
# -> 1 2

Ejemplo del mundo real: global_settings.py de Django (aunque en la configuración de las aplicaciones de Django se utiliza al importar el objeto django.conf.settings ).


3
Mejor porque evita posibles conflictos de espacio de nombres
bgw

¿Qué sucede si el módulo que está importando, en este caso a.py, contiene main()? ¿Importa?
sedeh

44
@sedeh: no. Si a.py también se ejecuta como una secuencia de comandos, use if __name__=="__main__"guard para evitar la ejecución de código inesperado en la importación.
jfs

66
En el mundo real, debes tener un poco de cuidado con esta solución. Si un programador recoge su variable 'global' usando 'de una variable de importación', (intente esta variación en c.py) obtendrá una copia de la variable en el momento de la importación.
Paul Whipp

1
@PaulWhipp: incorrecto (pista: uso id()para verificar identidad)
jfs

25

Creo que hay muchas circunstancias en las que tiene sentido y simplifica la programación para tener algunos globales que se conocen en varios módulos (estrechamente acoplados). En este espíritu, me gustaría profundizar un poco en la idea de tener un módulo de globales que sea importado por aquellos módulos que necesitan hacer referencia a ellos.

Cuando solo hay uno de esos módulos, lo llamo "g". En él, asigno valores predeterminados para cada variable que intento tratar como global. En cada módulo que usa alguno de ellos, no uso "from g import var", ya que esto solo da como resultado una variable local que se inicializa desde g solo en el momento de la importación. Hago la mayoría de las referencias en la forma g.var y la "g". sirve como un recordatorio constante de que estoy tratando con una variable que es potencialmente accesible para otros módulos.

Si el valor de dicha variable global se va a usar con frecuencia en alguna función de un módulo, entonces esa función puede hacer una copia local: var = g.var. Sin embargo, es importante darse cuenta de que las asignaciones a var son locales y que g.var global no se puede actualizar sin hacer referencia explícita a g.var en una asignación.

Tenga en cuenta que también puede tener múltiples módulos globales de este tipo compartidos por diferentes subconjuntos de sus módulos para mantener las cosas un poco más controladas. La razón por la que uso nombres cortos para mis módulos globales es para evitar saturar demasiado el código con la aparición de ellos. Con solo un poco de experiencia, se vuelven lo suficientemente mnemotécnicos con solo 1 o 2 caracteres.

Todavía es posible hacer una asignación a, por ejemplo, gx cuando x no estaba definido en g, y un módulo diferente puede acceder a gx. Sin embargo, aunque el intérprete lo permite, este enfoque no es tan transparente, y evito eso. Todavía existe la posibilidad de crear accidentalmente una nueva variable en g como resultado de un error tipográfico en el nombre de la variable para una asignación. A veces, un examen de dir (g) es útil para descubrir cualquier nombre sorpresa que pueda haber surgido por dicho accidente.


77
Esta observación interesante resolvió mi problema: "No uso" from g import var ", ya que esto solo da como resultado una variable local que se inicializa desde g solo en el momento de la importación". Parece razonable suponer que "de ... importación" es lo mismo que "importar", pero esto no es cierto.
Curtis Yallop

24

Defina un módulo (llámelo "globalbaz") y tenga las variables definidas dentro de él. Todos los módulos que usan este "pseudoglobal" deberían importar el módulo "globalbaz" y referirse a él usando "globalbaz.var_name"

Esto funciona independientemente del lugar del cambio, puede cambiar la variable antes o después de la importación. El módulo importado utilizará el último valor. (Probé esto en un ejemplo de juguete)

Para aclarar, globalbaz.py se ve así:

var_name = "my_useful_string"

9

Puede pasar los globales de un módulo a otro:

En el módulo A:

import module_b
my_var=2
module_b.do_something_with_my_globals(globals())
print my_var

En el módulo B:

def do_something_with_my_globals(glob): # glob is simply a dict.
    glob["my_var"]=3

7

Las variables globales suelen ser una mala idea, pero puede hacerlo asignando a __builtins__:

__builtins__.foo = 'something'
print foo

Además, los módulos en sí mismos son variables a las que puede acceder desde cualquier módulo. Entonces, si define un módulo llamado my_globals.py:

# my_globals.py
foo = 'something'

Entonces puedes usar eso desde cualquier lugar también:

import my_globals
print my_globals.foo

El uso de módulos en lugar de modificar __builtins__es generalmente una forma más limpia de hacer globales de este tipo.


3
__builtins__es una peculiaridad de CPython, realmente no deberías usarla - mejor uso __builtin__(o builtinsen Python3) como muestra la respuesta aceptada
Tobias Kienzler

5

Ya puede hacer esto con variables de nivel de módulo. Los módulos son los mismos sin importar desde qué módulo se estén importando. Por lo tanto, puede hacer que la variable sea una variable de nivel de módulo en cualquier módulo que tenga sentido colocarla y acceder a ella o asignarla desde otros módulos. Sería mejor llamar a una función para establecer el valor de la variable, o para que sea una propiedad de algún objeto singleton. De esa manera, si termina necesitando ejecutar algún código cuando la variable ha cambiado, puede hacerlo sin romper la interfaz externa de su módulo.

Por lo general, no es una buena manera de hacer las cosas, ya que rara vez lo es el uso de los globales, pero creo que esta es la forma más limpia de hacerlo.


3

Quería publicar una respuesta de que hay un caso en el que no se encontrará la variable.

Las importaciones cíclicas pueden romper el comportamiento del módulo.

Por ejemplo:

first.py

import second
var = 1

second.py

import first
print(first.var)  # will throw an error because the order of execution happens before var gets declared.

main.py

import first

En este ejemplo, debería ser obvio, pero en una base de código grande, esto puede ser realmente confuso.


1

Esto suena como modificar el __builtin__espacio de nombres. Para hacerlo:

import __builtin__
__builtin__.foo = 'some-value'

No utilice __builtins__directamente (observe la "s" adicional), aparentemente puede ser un diccionario o un módulo. Gracias a ΤΖΩΤΖΙΟΥ por señalar esto, se puede encontrar más aquí .

Ahora fooestá disponible para usar en todas partes.

No recomiendo hacer esto en general, pero el uso de esto depende del programador.

La asignación debe hacerse como se indicó anteriormente, solo la configuración foo = 'some-other-value'solo lo establecerá en el espacio de nombres actual.


1
Recuerdo (de comp.lang.python) que se debe evitar el uso directo de componentes incorporados ; en su lugar, importe el builtin y utilícelo, como sugirió Curt Hagenlocher.
tzot

1

Lo uso para un par de funciones primitivas incorporadas que sentí que realmente faltaban. Un ejemplo es una función de búsqueda que tiene la misma semántica de uso que filtro, mapa, reducción.

def builtin_find(f, x, d=None):
    for i in x:
        if f(i):
            return i
    return d

import __builtin__
__builtin__.find = builtin_find

Una vez que esto se ejecuta (por ejemplo, al importar cerca de su punto de entrada) todos sus módulos pueden usar find () como si, obviamente, estuviera integrado.

find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.

Nota: Puede hacer esto, por supuesto, con un filtro y otra línea para probar la longitud cero, o con reducir en un tipo de línea extraña, pero siempre sentí que era extraño.


1

Podría lograr variables modificables (o mutables ) entre módulos utilizando un diccionario:

# in myapp.__init__
Timeouts = {} # cross-modules global mutable variables for testing purpose
Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60

# in myapp.mod1
from myapp import Timeouts

def wait_app_up(project_name, port):
    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
    # ...

# in myapp.test.test_mod1
from myapp import Timeouts

def test_wait_app_up_fail(self):
    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
    with self.assertRaises(hlp.TimeoutException) as cm:
        wait_app_up(PROJECT_NAME, PROJECT_PORT)
    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak

Al iniciar test_wait_app_up_fail, la duración real del tiempo de espera es de 3 segundos.


1

Me preguntaba si sería posible evitar algunas de las desventajas del uso de variables globales (véase, por ejemplo, http://wiki.c2.com/?GlobalVariablesAreBad ) mediante el uso de un espacio de nombres de clase en lugar de un espacio de nombres global / módulo para pasar valores de variables . El siguiente código indica que los dos métodos son esencialmente idénticos. Hay una ligera ventaja en el uso de espacios de nombres de clase como se explica a continuación.

Los siguientes fragmentos de código también muestran que los atributos o variables pueden crearse y eliminarse dinámicamente tanto en espacios de nombres globales / de módulo como en espacios de nombres de clase.

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

Llamo a este módulo 'muro' ya que se usa para rebotar variables. Actuará como un espacio para definir temporalmente variables globales y atributos de toda la clase del 'enrutador' de clase vacía.

source.py

import wall
def sourcefn():
    msg = 'Hello world!'
    wall.msg = msg
    wall.router.msg = msg

Este módulo importa el muro y define una sola función sourcefnque define un mensaje y lo emite mediante dos mecanismos diferentes, uno a través de las funciones globales y otro a través de la función del enrutador. Tenga en cuenta que las variables wall.msgy wall.router.messagese definen aquí por primera vez en sus respectivos espacios de nombres.

dest.py

import wall
def destfn():

    if hasattr(wall, 'msg'):
        print 'global: ' + wall.msg
        del wall.msg
    else:
        print 'global: ' + 'no message'

    if hasattr(wall.router, 'msg'):
        print 'router: ' + wall.router.msg
        del wall.router.msg
    else:
        print 'router: ' + 'no message'

Este módulo define una función destfnque utiliza los dos mecanismos diferentes para recibir los mensajes emitidos por la fuente. Permite la posibilidad de que la variable 'msg' no exista.destfntambién elimina las variables una vez que se han mostrado.

main.py

import source, dest

source.sourcefn()

dest.destfn() # variables deleted after this call
dest.destfn()

Este módulo llama a las funciones definidas previamente en secuencia. Después de la primera llamada a dest.destfnlas variables wall.msgy wall.router.msgya no existen.

La salida del programa es:

global: Hola mundo!
enrutador: ¡Hola mundo!
global: sin mensaje
enrutador: sin mensaje

Los fragmentos de código anteriores muestran que los mecanismos módulo / global y clase / clase variable son esencialmente idénticos.

Si se van a compartir muchas variables, la contaminación del espacio de nombres se puede gestionar mediante el uso de varios módulos de tipo de pared, por ejemplo, wall1, wall2, etc. o definiendo varias clases de tipo de enrutador en un solo archivo. Este último es un poco más ordenado, por lo que tal vez representa una ventaja marginal para el uso del mecanismo de variable de clase.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.