2010-01-04 10 views

Respuesta

11

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

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

Por lo tanto, he creado una clase cuyos atributos de objetos están diseñados de la manera que usted necesita (y se pueden crear 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 desbordamiento 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() 
+0

¡Felicitaciones! Gracias a su arduo trabajo, el mundo de la programación tiene una solución más que se abrirá paso en el corazón de numerosas aplicaciones críticas. – yfeldblum

+0

Y, después de todo, las "variables especiales" de Lisp no son tan terribles, ¿verdad? Son como variables de entorno en bash. Lo que es terrible son los idiomas en los que el alcance dinámico es el predeterminado. Afortunadamente, no quedan muchos. –

+1

Me alegra que la mayoría de los lenguajes reales no usen el alcance dinámico ... y, sin embargo, he escrito un montón de Emacs Lisp, que sí; y se siente completamente natural para mí. (Emacs Lisp obtuvo recientemente el alcance léxico como una opción, y nunca me he molestado en usarlo :-) – offby1

-5

Alcance dinámico considerado dañino.

No lo use; 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 los métodos begin que se llaman en la primera línea de sus funciones que usan ámbitos dinámicos, end, get y set. Los métodos get y set deben implementar la búsqueda de la cadena de llamadas para nombres de variables donde la cadena de llamadas se implementa mediante begin y end. Luego refactorice su código para eliminar los ámbitos dinámicos.

+3

El alcance dinámico * puede * ser una característica increíblemente útil en los idiomas que lo soportan bien. He hecho pequeños cambios (de 3 a 4 líneas) en programas grandes de Common Lisp que habrían necesitado modificaciones enormes (pero mecánicamente simples) sin él. A veces es la solución natural a un problema. Dicho esto, no es del todo natural en Python, y no recomendaría portarlo directamente, eso parece una receta para el dolor de mantenimiento. – Ken

+2

Hay buenos usos para el alcance dinámico, especialmente cuando se configuran configuraciones relativamente globales que no se quieren enhebrar a través de los argumentos de cada función (por ejemplo, dónde imprimir la salida de stdout). Por supuesto, las variables de ámbito dinámico deben estar bien marcadas y se debe desalentar su uso general. La siguiente solución de Jason Orendorff es un buen compromiso para Python y ha ayudado a simplificar parte de mi código. – Winterstream

11

Aquí hay algo que funciona un poco como variables especiales de Lisp, pero se ajusta 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() 

Se puede utilizar la siguiente manera:

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 últimos durante 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.

+3

Esto es una solución de compromiso entre "no hacer eso" y la inspección de la pila (que parece que sería lenta y difícil de verificar). –

+2

Buena solución. Es bastante explícito (por lo que incluso si sorprende a alguien, no se mezcla con la semántica ordinaria de Python). Encuentro este tipo de enfoque muy útil para el trazado científico, ya que hay muchas configuraciones que me gustaría establecer en algún momento en la pila de llamadas y es un dolor tener que llevarlas hasta la función donde el trazado real sucede. – Winterstream

5

La expresión de Python correspondiente a las variables Lisp "especiales" o de ámbito dinámico es "almacenamiento local de subprocesos".

Aquí es una buena discusión: What is "thread local storage" in Python, and why do I need it?

Si desea emular completamente variables especiales de Lisp, incluida la declaración let, se puede utilizar un gestor 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() 
Cuestiones relacionadas