¿Cómo modificar dinámicamente el espacio de nombres local de una función?

NB: Esta pregunta asume Python 2.7.3.

Estoy buscando un enfoque sensato para modificar dinámicamente el espacio de nombres local de una función, preferiblemente de una manera que agregue el menor desorden a la función del cuerpo.

Lo que tengo en mente se vería así:

import os from namespace_updater import update_locals def somefunc(x, y, z): # ... # ... # this and that # ... # ... if os.environ.get('FROBNICATE'): from frobnitz import frobnicate update_locals(frobnicate(locals())) # # life goes on, possibly with duly frobnicated local variables... # ... # ... # ... 

¡Gracias!


PD: A continuación hay enfoques que no funcionan.

El enfoque más ingenuo de esto sería algo como:

 locals().update(new_locals(locals()) 

… pero la documentación para los locals() advierte muy explícitamente contra la dependencia de dicho vudú para modificar las variables locales, así que no envíe esto como respuesta (a menos que pueda presentar un caso excelente para no tener en cuenta la advertencia de la documentación).

Lo siguiente en la escala de ingenuidad es algo así como

 for k, v in new_locals(locals()).items(): exec ('%s = v' % k) 

AFAICT, dicho código no puede estar “fuera del camino” (es decir, tiene que estar en el cuerpo de la función), lo cual no es ideal. Pero el verdadero factor decisivo es que el hack del exec ('%s = v' % k) puede llevar a algunos errores extraños.

Cuando escribo “bugs extraños”, lo que quiero decir es “bugs que parecen extraños para alguien con un conocimiento tan tenue de exec ('%s = v' % k) como el mío”. ¿Qué tan tenue es mi comprensión de este truco? Para responder esto, considere el siguiente script. Tiene tres variantes: (1) exactamente como se muestra; (2) después de borrar el # de la línea 18; (3) después de eliminar el primer # en ambas líneas 15 y 18 (es decir, para esta variante, no se comenta ningún código). No podría haber predicho el comportamiento de las variantes (2) y (3) de este script. Ni siquiera podría haber predicho con más del 50% de confianza el comportamiento de la variante (1). Eso es lo tenue que es mi comprensión del hack exec ('%s = v' % k) . A menos que pueda predecir con confianza y correctamente cómo se comportarán las tres variantes de este script (bajo python 2.7), es seguro decir que su comprensión de la situación es tan tenue como la mía, y probablemente debería mantenerse alejado de los exec ('%s = v' % k) también.

 x = 'global x' # 01 y = 'global y' # 02 def main(): # 03 x = 'local x' # 04 y = 'local y' # 05 run(locals()) # 06 print 'OK' # 07 return 0 # 08 # 09 def run(namespace): # 10 global y # 11 print locals().keys() # 12 for k, v in namespace.items(): # 13 print '%s <- %r' % (k, v) # 14 exec ('%s = v' % k) #in locals() # 15 print locals().keys() # 16 x = x # 17 #z = lambda: k # 18 print x # 19 print y # 20 # 21 exit(main()) # 22 

Presentaré el único enfoque en el que puedo pensar que es casi razonable, y luego intentaré convencerlo de que no lo use.

 def process(**kw): mycode = """\ print 'Value of foo is %s' % (foo,) print 'Value of bar is %s' % (bar,) """ exec mycode in kw vars = {'foo': 2, 'bar': 3} process(**vars) 

Con este enfoque, tiene al menos algo de protección contra los ataques de inyección de código. El diccionario que contiene las “variables locales” del código se especifica explícitamente, por lo que tiene un control completo sobre cuál será el espacio de la variable cuando ejecute la sentencia exec . No tiene que piratear las partes internas de los objetos de función u otros similares.

Sé que el módulo decorador usa exec en la implementación de @decorator para manipular nombres de argumentos en funciones creadas dinámicamente, y puede haber otros módulos comunes que lo usen. Pero solo he estado en una situación en la que exec fue una clara victoria sobre las alternativas en Python, y una para eval .

No veo tal situación en tu pregunta. A menos que mycode desde arriba tenga que hacer algo realmente funky, como crear una función con nombres de argumentos en kw , es probable que pueda escribir simplemente el código de forma sencilla, y tal vez usar locals() en un apuro.

 def process(**kw): print 'Value of foo is %s' % (kw['foo'],) print 'Value of bar is %s' % (kw['bar'],) process(foo=2, bar=3) 

Puede ser algo asi

 def foo(): print(x) foo.__globals__["x"] = "Hello Python" foo() 

Desafortunadamente, esto no funciona en el cuerpo de funciones si se ha definido la variable

 def foo(flag): x = "Hello World" if flag: foo.__globals__["x"] = "Hello Python" print(x) 

Imprime Hello World en ambas banderas es Verdadero o Falso

No estoy seguro si es posible solo con una función externa. He creado un fragmento de código:

 def get_module_prefix(mod, localsDict): for name, value in localsDict.iteritems(): if value == mod: return name raise Exception("Not found") def get_new_locals(mod, localsDict): modulePrefix = get_module_prefix(mod, localsDict) stmts = [] for name in dir(mod): if name.startswith('_'): continue if name not in localsDict: continue stmts.append("%s = %s.%s" % (name, modulePrefix, name)) return "\n".join(stmts) def func(someName): from some.dotted.prefix import some.dotted.name #here we update locals exec(get_new_locals(some.dotted.name, "some.dotted.name", locals())) print locals() print someName # value taken from aModule instead of parameter value func(5) 

dónde:

  • get_module_prefix se usa para encontrar el nombre con el que se importa el módulo,
  • get_new_locals devuelve instrucciones de asignación que pueden usarse para actualizar locales,

La actualización real de los locales se realiza en la línea exec(get_new_locals(some.dotted.name, locals())) donde simplemente ejecutamos las declaraciones de asignación en las que asignamos valores del módulo a las variables locales.

No estoy seguro de si es lo que realmente mencionas.