2011-01-10 13 views
7

Dar este diccionario:.¿Cómo puedo cargar todas las claves de un dict como variables locales, un mejor enfoque?

>>> options = {'DATABASES': {'default': {'ENGINE': 'django.db.backends.sqlite3'}}} 

¿Cuál sería la mejor manera de conseguir esto ?:

>>> foo(options) 
>>> print DATABASES 
{'default': {'ENGINE': 'django.db.backends.sqlite3'}} 

Estoy solución de este como los locales() actualización (opciones), pero yo estaba pensando, si hay tal vez una mejor solución.

+1

La mejor manera es no hacer este tipo de cosas peligrosas en el primer lugar. Y afortunadamente, una función por sí sola no podrá jugar con el espacio de nombres actual (de hecho, puede hacerlo con el global y quizás con más si usa el hacker de reflexión maligna, ¡pero no lo piense!) – delnan

+0

@ delnan Entiendo que usar locals() es feo.Mira, después de analizar un archivo de configuración, obtengo un diccionario de opciones, así que ¿cómo se pueden reemplazar los valores predeterminados en la configuración sin usar los locales?) ¿ –

+1

? Mejor que no lo haga. Porque hacerlo significa sobrescribir sus variables con la entrada del usuario, que está solo un paso por encima de 'eval'. Solo continúa con el diccionario. – delnan

Respuesta

7
import inspect 

allowed_vars = set(["min_", "max_", "path", ...]) 
def update_globals(dic): 
    caller_frame = inspect.currentframe(1) 
    globals = caller_frame.f_globals 
    # here, you _could_ simply do globals.update(dic) 
    # but it is evil 
    for key, value in dic.items(): 
     #here you should carefully verify each key, and value for not 
     #not dangerous pairs, with stuff like: 
     #if key not in allowed_vars: 
     # sys.stderr.write("Warning: invalid variable in configuration update\n") 
     #  continue 
     #if type(value) not in (string, int, float): 
     # #(issue error) 
     # continue 
     globals[key] = value 

Ejemplo:

>>> a 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
NameError: name 'a' is not defined 
>>> update_globals({"a": 5}) 
>>> a 
5 

actualización 2016-06 Un hace dos semanas me había armado el paquete Python extradict - que está disponible en pypi now Una de sus características es el gerente MapGetter contexto que permite exactamente lo que se está pidiendo haciendo algo a lo largo:

.
from extradict import MapGetter 

def myfunc(): 
    options = {'DATABASES': {'default': {'ENGINE': 'django.db.backends.sqlite3'}}} 
    with MapGetter(options) as options: 
     from options import DATABASES 
... 

Y otros usos normales "from .... import ....", pero de un diccionario u objeto de correlación (incluido un dict predeterminado).

+0

Soy consciente de que modificar locales o globales es algo peligroso, pero su método parece funcionar como lo necesito. Probablemente en el futuro cambiaría el comportamiento de mi analizador para la configuración –

+0

No es modificar local o global lo que es peligroso per se - eso es parte del dinamismo de Python y tenemos que hacer uso de él. Lo peligroso es elegir datos de una fuente no verificada (normalmente usuarios remotos) y convertirlos en objetos python "vivos" en el programa en ejecución. Siempre que agregue las protecciones que sugiero arriba, no debería haber ningún problema al usar este código. – jsbueno

0

No se pueden modificar locals de funciones en tiempo de ejecución, ya que la lista de nombres de variables locales es una parte estática del objeto de función compilado.

>>> def a(): b = 5 
... 
>>> a.func_code.co_varnames 
('b',) 

Esto funcionará en el ámbito global sólo porque locals() es lo mismo que globals() allí, y las variables globales se almacenan en un diccionario, que es dinámico (a diferencia de los locales de la función).

>>> locals() is globals() 
True 

Parece que va a actualizar los valores en un módulo de configuración de Django de otra fuente. No diría que esto es necesariamente malo, pero debe usar globals() en lugar de locals() por motivos de claridad.

3

Como goldmab señaló, modificando la salida de gente() dentro de una función no funcionará:

SyntaxError: invalid syntax 
>>> def foo(): 
...  locals().update({'a': 1}) 
...  print a 
... 
>>> foo() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 3, in foo 
NameError: global name 'a' is not defined 

Esto no es realmente una manera muy limpia, pero sería hacer el trabajo:

>>> def foo(): 
...  options = {'DATABASES': {'default': {'ENGINE': 'django.db.backends.sqlite3'}}} 
...  for k, v in options.items(): 
...   exec('%s = v' % k) 
...  print DATABASES 
... 
>>> foo() 
{'default': {'ENGINE': 'django.db.backends.sqlite3'}} 

Tenga en cuenta, por cierto, que cada tecla en su dict debería estar bien como una variable. Entonces, por ejemplo, si el diccionario contiene 'DATABASE-USERNAME' como clave, intentar asignar eso a una variable provocaría una excepción. Además, hacer esto lo haría vulnerable a los ataques de inyección de código si obtiene el diccionario de opciones de una fuente no confiable. (Una de las claves podría decir algo como, "import os; os.system ('sudo adduser Script Kiddie'); ..."

3

creo que lo que quiere es simplemente:

globals().update(**options) 
Cuestiones relacionadas