python variables lisp scope dynamic-scope

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



lisp scope (4)

Estoy traduciendo un código de Lisp a Python.

En lisp, puede tener un constructo de let con las variables introducidas declaradas como especiales y teniendo así un alcance dinámico. (Ver http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping )

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


Alcance dinámico considerado dañino.

No lo uses; no lo emule

Si necesita emularlo, defina un módulo dynamic_scope para emular este comportamiento e importar el módulo en todos los archivos fuente. Este módulo debe tener métodos para begin que se llama en la primera línea de sus funciones que usan ámbitos dinámicos, end , get y set . Los métodos get y set deberían implementar la búsqueda de la cadena de llamadas para nombres de variables donde la cadena de llamadas se implementa por begin y end . Luego refactorice su código para eliminar los ámbitos dinámicos.


Aquí hay algo que funciona un poco como las variables especiales de Lisp, pero encaja un poco mejor en 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 por la duración del bloque with .

Tenga en cuenta que si utiliza subprocesos, definitivamente querrá un _stack diferente para cada subproceso. Puede usar threading.local para implementar eso.


Siento que la justicia es clara en su razonamiento aquí.

Por otro lado, no puedo resistir la implementación de una prueba de concepto para otro paradigma de programación "antinatural" para Python: simplemente me encanta hacer esto. :-)

Por lo tanto, creé una clase cuyos atributos de objetos están diseñados de la misma forma que lo requieren (y pueden crearse dinámicamente). Como dije, es solo una prueba de concepto, pero creo que los errores más habituales (como tratar de acceder a un ámbito variable en el que no está definido) deberían tener errores, incluso los incorrectos (IndexError) debido a un subdesbordamiento de pila 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() D.c = "old" print D.c def a(): print D.c a() def b(): D.c = "new" a() b() a() def c(): D.c = "newest" a() b() a() c() a() c()


La expresión idiomática de Python correspondiente a las variables "especiales" o de ámbito dinámico 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 completamente las variables especiales de Lisp, incluida la instrucción 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 (patentemente tonto, pero muestra la característica 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()