2010-01-04 20 views

risposta

11

Sento che la giustizia è chiara nel suo ragionamento.

D'altra parte - Non posso resistere alla realizzazione di proof of concept per ancora un altro paradigma di programmazione "innaturale" per Python - Semplicemente mi piace farlo. :-)

Quindi, ho creato una classe i cui attributi degli oggetti vengono eseguiti come richiesto (e possono essere creati dinamicamente). Come ho detto, è solo in uno stato di proof of concept - ma penso che gli errori più comuni, (come tentare di accedere a una variabile in un scope non è definito affatto) dovrebbero avere errori sollevati, anche se non quelli corretti (IndexError a causa di un underflow dello stack, invece di AttributeError, per esempio)

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

Congratulazioni! Grazie al tuo duro lavoro, il mondo della programmazione ha ancora una soluzione in più che farà strada nei cuori di numerose applicazioni critiche! – yfeldblum

+0

E dopotutto, le "variabili speciali" di Lisp non sono così terribili, vero? Sono come le variabili di ambiente in bash. Ciò che è terribile sono le lingue in cui l'ambito dinamico è l'impostazione predefinita. Fortunatamente non molti di quelli sono rimasti. –

+1

Sono contento che la maggior parte dei linguaggi reali non utilizzi l'ambito dinamico ... eppure ho scritto una tonnellata di Emacs Lisp, che lo fa; e mi sembra assolutamente naturale. (Emacs Lisp ha di recente ottenuto l'opzione lessicale come opzione, e non mi sono nemmeno preoccupato di usarlo :-) – offby1

-5

Ambito dinamico considerato pericoloso.

Non utilizzarlo; non emularlo.

Se è necessario emularlo, definire un modulo dynamic_scope per emulare questo comportamento e importare il modulo in tutti i file di origine. Questo modulo deve avere i metodi begin chiamati nella prima riga delle funzioni che utilizzano ambiti dinamici, end, get e set. I metodi get e set devono implementare la ricerca della catena di chiamate per i nomi di variabili in cui la catena di chiamate viene implementata da begin e end. Quindi rifatta il tuo codice per eliminare gli ambiti dinamici.

+3

L'ambito dinamico * può * essere una funzione incredibilmente utile in lingue che lo supportano bene. Ho apportato piccole (3-4 linee) modifiche a grandi programmi Common Lisp che avrebbero richiesto enormi (ma meccanicamente semplici) modifiche senza di essa. A volte è la soluzione naturale a un problema. Detto questo, non è del tutto naturale in Python, e non suggerirei di portarlo direttamente - sembra una ricetta per il dolore di mantenimento. – Ken

+2

Ci sono buoni utilizzi per lo scope dinamico, specialmente quando si impostano impostazioni relativamente globali che non si desidera passare attraverso gli argomenti di ogni funzione (ad esempio, dove stampare l'output di stdout). Ovviamente, le variabili con scope dinamico dovrebbero essere ben marcate e il loro uso generale scoraggiato. La seguente soluzione di Jason Orendorff è un buon compromesso per Python e ha contribuito a semplificare parte del mio codice. – Winterstream

11

Ecco qualcosa che funziona un po 'come variabili speciali di Lisp, ma si adatta un po' meglio in 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() 

Si può usare in questo modo:

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

Gli effetti della env.let durano per tutta la durata del blocco with.

Si noti che se si utilizzano thread, si vorrà sicuramente un diverso _stack per ogni thread. Potresti usare threading.local per implementarlo.

+3

Questo è inteso come un compromesso tra "non farlo" e l'ispezione dello stack (che sembra che sia lenta e difficile da verificare). –

+2

Bella soluzione. È abbastanza esplicito (quindi, anche se sorprende qualcuno, non fa confusione con la normale semantica di Python). Trovo questo tipo di approccio molto utile per il disegno scientifico, dal momento che ci sono molte impostazioni che vorrei impostare ad un certo punto nello stack di chiamate ed è un dolore doverle portare fino alla funzione dove succede la trama vera e propria – Winterstream

5

L'idioma Python corrispondente a Lisp "speciale" o variabili con ambito dinamico è "archiviazione locale del thread".

Ecco una buona discussione: What is "thread local storage" in Python, and why do I need it?

Se si desidera emulare completamente variabili speciali di Lisp, compresa la dichiarazione let, è possibile utilizzare un gestore di contesto:

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) 

Esempio (palesemente stupido, ma mostra la funzione di rientro):

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() 
Problemi correlati