¿Cómo crear variables de ámbito dinámicas en Python?

Estoy traduciendo algún código de lisp a Python.

En lisp, puede tener una construcción de dejar con las variables introducidas declaradas como especiales y, por lo tanto, tener un scope dynamic. (Ver http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping )

¿Cómo puedo hacer lo mismo en Python? Parece que el lenguaje no admite esto directamente, si es cierto, ¿cuál sería una buena manera de emularlo?

Siento que la justicia es sencillamente correcta en su razonamiento aquí.

Por otro lado, no puedo resistirme a implementar la prueba de concepto para otro paradigma de progtwigción “no natural” para Python, simplemente me encanta hacer esto. 🙂

Por lo tanto, creé una clase cuyos atributos de objetos se escogen de la forma en que lo requieren (y se pueden crear dinámicamente). Como dije, está en un estado de prueba de concepto, pero creo que la mayoría de los errores habituales (como intentar acceder a una variable en un ámbito que no está definido en absoluto) deberían tener errores, incluso si no son los correctos (IndexError debido a un desbordamiento de stack en lugar de AttributeError, por ejemplo)

import inspect class DynamicVars(object): def __init__(self): object.__setattr__(self, "variables", {}) def normalize(self, stackframe): return [hash(tpl[0]) for tpl in stackframe[1:]] def __setattr__(self, attr, value): stack = self.normalize(inspect.stack()) d = {"value": value, "stack": stack} if not attr in self.variables: self.variables[attr] = [] self.variables[attr].append(d) else: our_value = self.variables[attr] if our_value[-1]["stack"] == stack: our_value[-1]["value"] = value elif len(stack) <= len(our_value): while our_value and stack != our_value["stack"]: our_value.pop() our_value.append(d) else: #len(stack) > len(our_value): our_value.append(d) def __getattr__(self, attr): if not attr in self.variables: raise AttributeError stack = self.normalize(inspect.stack()) while self.variables[attr]: our_stack = self.variables[attr][-1]["stack"] if our_stack == stack[-len(our_stack):]: break self.variables[attr].pop() else: raise AttributeError return self.variables[attr][-1]["value"] # for testing: def c(): D = DynamicVars() Dc = "old" print Dc def a(): print Dc a() def b(): Dc = "new" a() b() a() def c(): Dc = "newest" a() b() a() c() a() c() 

Aquí hay algo que funciona un poco como las variables especiales de Lisp, pero se ajusta un poco mejor a Python.

 _stack = [] class _EnvBlock(object): def __init__(self, kwargs): self.kwargs = kwargs def __enter__(self): _stack.append(self.kwargs) def __exit__(self, t, v, tb): _stack.pop() class _Env(object): def __getattr__(self, name): for scope in reversed(_stack): if name in scope: return scope[name] raise AttributeError("no such variable in environment") def let(self, **kwargs): return _EnvBlock(kwargs) def __setattr__(self, name, value): raise AttributeError("env variables can only be set using `with env.let()`") env = _Env() 

Puedes usarlo así:

 with env.let(bufsize=8192, encoding="ascii"): print env.bufsize # prints 8192 a() # call a function that uses env.bufsize or env.encoding 

Los efectos de env.let duran toda la duración del bloque with .

Tenga en cuenta que si usa hilos, definitivamente querrá un _stack diferente para cada hilo. Podrías usar threading.local para implementar eso.

El idioma de Python correspondiente a las variables “especiales” o dinámicas de Lisp es “almacenamiento local de subprocesos”.

Aquí hay una buena discusión: ¿Qué es el “almacenamiento local de subprocesos” en Python y por qué lo necesito?

Si desea emular por completo las variables especiales de Lisp, incluida la statement let, puede usar un administrador de contexto:

 from __future__ import with_statement # if Python 2.5 from contextlib import contextmanager import threading dyn = threading.local() @contextmanager def dyn_vars(**new): old = {} for name, value in new.items(): old[name] = getattr(dyn, name, None) setattr(dyn, name, value) yield for name, value in old.items(): setattr(dyn, name, value) 

Ejemplo (evidentemente tonto, pero muestra la función de reentrada):

 def greet_self(): print 'Hi', dyn.who_am_I def greet_selves(): with dyn_vars(who_am_I='Evil Twin'): greet_self() greet_self() with dyn_vars(who_am_I='Tobia'): greet_selves() 

Ámbito dynamic considerado perjudicial.

No lo uses; no lo emules

Si necesita emularlo, defina un módulo dynamic_scope para emular este comportamiento e importe el módulo en todos los archivos de origen. Este módulo debe tener métodos de begin que se llaman en la primera línea de sus funciones que usan ámbitos dynamics, end , get y set . Los métodos de get y set deben implementarse buscando en la cadena de llamadas los nombres de las variables donde se implementa la cadena de llamadas al begin y al end . Luego refactoriza tu código para eliminar los ámbitos dynamics.