2009-12-29 7 views
145

¿Hay alguna forma de configurar una variable global dentro de un módulo? Cuando traté de hacerlo de la manera más obvia como aparece a continuación, el intérprete de Python dijo que la variable __DBNAME__ no existía.¿Cómo crear variables de módulo completo en Python?

... 
__DBNAME__ = None 

def initDB(name): 
    if not __DBNAME__: 
     __DBNAME__ = name 
    else: 
     raise RuntimeError("Database name has already been set.") 
... 

Y después de importar el módulo en un archivo diferente

... 
import mymodule 
mymodule.initDB('mydb.sqlite') 
... 

Y el rastreo fue: UnboundLocalError: local variable '__DBNAME__' referenced before assignment

¿Alguna idea? Estoy tratando de configurar un singleton utilizando un módulo, según la recomendación this fellow's.

+6

Gracias a todos por las excelentes respuestas! Aprendí mucho más sobre Python de lo que esperaba. – daveslab

Respuesta

172

Esto es lo que está pasando.

Primero, las únicas variables globales que realmente tiene Python son las variables con ámbito de módulo. No puedes hacer una variable que sea verdaderamente global; Todo lo que puedes hacer es crear una variable en un ámbito particular. (Si crea una variable dentro del intérprete de Python y luego importa otros módulos, su variable está en el alcance más externo y, por tanto, global dentro de su sesión de Python).

Todo lo que tiene que hacer para crear una variable module-global es solo asigne un nombre.

imaginar un archivo llamado foo.py, que contiene esta sola línea:

X = 1 

Ahora imaginemos lo importa.

import foo 
print(foo.X) # prints 1 

Sin embargo, supongamos que desea utilizar una de las variables de módulo de alcance mundial como dentro de una función, como en el ejemplo. El valor predeterminado de Python es suponer que las variables de función son locales. Simplemente agrega una declaración global en su función, antes de intentar usar el global.

def initDB(name): 
    global __DBNAME__ # add this line! 
    if __DBNAME__ is None: # see notes below; explicit test for None 
     __DBNAME__ = name 
    else: 
     raise RuntimeError("Database name has already been set.") 

Por cierto, para este ejemplo, el simple análisis de if not __DBNAME__ es adecuada, ya que cualquier valor de cadena que no sea una cadena vacía evaluará cierto, por lo que cualquier nombre de base de datos real evaluará cierto. Pero para las variables que pueden contener un valor numérico que podría ser 0, no puede simplemente decir if not variablename; en ese caso, debe probar explícitamente None usando el operador is. Modifiqué el ejemplo para agregar una prueba explícita None. La prueba explícita para None nunca es incorrecta, así que de manera predeterminada la uso.

Finalmente, como han señalado otros en esta página, dos guiones subrayan a Python que desea que la variable sea "privada" para el módulo. Si alguna vez hace un import * from mymodule, Python no importará nombres con dos guiones bajos principales en su espacio de nombres. Pero si solo hace un simple import mymodule y luego dice dir(mymodule) verá las variables "privadas" en la lista, y si se refiere explícitamente a mymodule.__DBNAME__ Python no le importará, solo le permitirá consultarlo. Los guiones bajos dobles son una pista importante para los usuarios de su módulo de que no desea que vuelvan a enlazar ese nombre con algún valor propio.

Se considera la mejor práctica en Python no hacer import *, pero para minimizar el acoplamiento y maximizar explícito, ya sea usando mymodule.something o por hacer explícita una importación como from mymodule import something.

EDITAR: Si, por alguna razón, necesita hacer algo como esto en una versión muy antigua de Python que no tiene la palabra clave global, hay una solución fácil. En lugar de establecer directamente una variable global de módulo, use un tipo mutable en el nivel global del módulo y almacene sus valores dentro de él.

En sus funciones, el nombre de la variable global será de solo lectura; no podrá volver a enlazar el nombre de la variable global real. (Si asigna un nombre de variable dentro de su función, solo afectará el nombre de la variable local dentro de la función). Pero puede usar ese nombre de variable local para acceder al objeto global real y almacenar datos dentro de él.

Se puede utilizar un list pero su código será feo:

__DBNAME__ = [None] # use length-1 list as a mutable 

# later, in code: 
if __DBNAME__[0] is None: 
    __DBNAME__[0] = name 

Un dict es mejor. Sin embargo, lo más conveniente es una instancia de clase, y sólo se puede utilizar una clase trivial: (. Usted realmente no necesita para capitalizar la variable nombre de la base de datos)

class Box: 
    pass 

__m = Box() # m will contain all module-level values 
__m.dbname = None # database name global in module 

# later, in code: 
if __m.dbname is None: 
    __m.dbname = name 

me gusta el azúcar sintáctica de solo usando __m.dbname en lugar de __m["DBNAME"]; parece la solución más conveniente en mi opinión. Pero la solución dict también funciona bien.

Con un dict puede usar cualquier valor hashable como clave, pero cuando esté satisfecho con los nombres que son identificadores válidos, puede usar una clase trivial como Box en el cuadro anterior.

+0

Excelente respuesta, @steveha, resumiendo toda la información correcta hasta el momento y aún dando la respuesta. – daveslab

+0

La última técnica (usando una clase) es perfecta. Gracias. –

+0

Dos guiones bajos principales conducirían a la manipulación de nombres. Por lo general, un solo guión bajo es suficiente para indicar que una variable debe considerarse privada. https://stackoverflow.com/questions/6930144/underscore-vs-double-underscore-with-variables-and-methods#6930223 – Rabiees

5

Para esto, necesita declarar la variable como global. Sin embargo, también se puede acceder a una variable global desde fuera del módulo utilizando module_name.var_name. Agregue esto como la primera línea de su módulo:

global __DBNAME__ 
+0

¿hay alguna forma de hacerlo accesible para todo el módulo, pero no disponible para ser llamado por module_name .__ DBNAME__? – daveslab

+0

Sí ... puedes poner la declaración global dentro de tu función para que sea "global" dentro del módulo (dentro de esa función ... tendrías que repetir la declaración global en cada función que use este global). Por ejemplo (perdonen el código en los comentarios): 'def initDB (nombre): \ n global __DBNAME__' –

+0

Gracias, Jarret. Desafortunadamente, cuando intento eso y ejecuto dir (mymodule) en la consola, muestra las variables como disponibles y puedo acceder a ellas. ¿Te estoy entendiendo mal? – daveslab

-5

Te estás enamorando de un sutil capricho. No puede volver a asignar variables de nivel de módulo dentro de una función de Python. Creo que esto está ahí para evitar que las personas vuelvan a asignar cosas dentro de una función por accidente.

Puede acceder al espacio de nombre del módulo, simplemente no debe intentar reasignarlo. Si su función asigna algo, se convierte automáticamente en una variable de función, y Python no buscará en el espacio de nombres del módulo.

que puede hacer:

__DB_NAME__ = None 

def func(): 
    if __DB_NAME__: 
     connect(__DB_NAME__) 
    else: 
     connect(Default_value) 

pero no se puede volver a asignar __DB_NAME__ dentro de una función.

Una solución:

__DB_NAME__ = [None] 

def func(): 
    if __DB_NAME__[0]: 
     connect(__DB_NAME__[0]) 
    else: 
     __DB_NAME__[0] = Default_value 

Nota, no estoy re-asignación de __DB_NAME__, sólo estoy modificando su contenido.

+3

Esto no es verdad. 'global' le permite establecer nombres de nivel de módulo. – dbn

16

La respuesta de Steveha fue útil para mí, pero omite un punto importante (uno que creo que era irónico). La palabra clave global no es necesaria si solo accede pero no asigna la variable en la función.

Si asigna la variable sin la palabra clave global, Python crea una nueva var local: el valor de la variable del módulo ahora estará oculto dentro de la función. Use la palabra clave global para asignar el módulo var dentro de una función.

Pylint 1.3.1 en Python 2.7 impone NOT utilizando global si no asigna la var.

module_var = '/dev/hello' 

def readonly_access(): 
    connect(module_var) 

def readwrite_access(): 
    global module_var 
    module_var = '/dev/hello2' 
    connect(module_var) 
33

acceso explícito a las variables de nivel de módulo accediendo a ellos explícitamente en el módulo


En resumen: La técnica descrita aquí es el mismo que en steveha's answer, excepción, que no se crea ningún objeto auxiliar artificial para las variables de ámbito explícito. En cambio, al objeto del módulo en sí se le asigna un puntero variable y, por lo tanto, proporciona un alcance explícito al acceder desde cualquier lugar. (como asignaciones en el alcance de la función local).

Piense en ello como auto para el módulo actual en lugar de la instancia actual!

# db.py 
import sys 

# this is a pointer to the module object instance itself. 
this = sys.modules[__name__] 

# we can explicitly make assignments on it 
this.db_name = None 

def initialize_db(name): 
    if (this.db_name is None): 
     # also in local function scope. no scope specifier like global is needed 
     this.db_name = name 
     # also the name remains free for local use 
     db_name = "Locally scoped db_name variable. Doesn't do anything here." 
    else: 
     msg = "Database is already initialized to {0}." 
     raise RuntimeError(msg.format(this.db_name)) 

As modules are cached and therefore import only once, puede importar db.py tantas veces en tantos clientes como usted quiere, la manipulación de la misma, estado universal:

# client_a.py 
import db 

db.initialize_db('mongo') 
# client_b.py 
from db import db_name 

if (db_name == 'mongo'): 
    db_name = None 

He utilizado esta receta para crear simple y ligero módulos de controlador(como se opone a las clases de controlador) desde que lo encontré. Como una ventaja adicional, creo que es bastante pitonica en general, ya que se adapta muy bien a la política de Pythons de Explícito es mejor que implícita.

+1

Me gusta que pueda usar el más preciso "desde la importación de db" en el segundo módulo, incluso si tiene que hacer un mayor "importar db" en el principal. Esto parece ser cierto si omites la magia 'sys' y usas 'global' en initialize_db. ¿Puedes comentar sobre los pros/contras de global vs. tu respuesta, ya que ambos parecen funcionar de la misma manera? –

+0

El ** pro ** para mí es que ya no necesitas manipulación del alcance. Usted otorga explícitamente el alcance al acceder a la variable nombre_bd de un objeto, que es el módulo. No tiene que declarar dónde vive el objeto con el que desea trabajar, antes de usarlo en cualquier momento. También puede tener variables locales denominadas db_name en las funciones del manejador, junto a this.db_name también. – timmwagener

+0

Para mí, esta parece ser la manera más limpia de hacer esto, pero mis errores se están frenando. ¿Estoy haciendo algo mal o tú/otros también tienen este problema? Muchas gracias, Chris – ThePosey

Cuestiones relacionadas