2009-01-20 16 views
25

considere el siguiente código:Simulación de una variable 'local estática' en Python

def CalcSomething(a): 
    if CalcSomething._cache.has_key(a): 
     return CalcSomething._cache[a] 
    CalcSomething._cache[a] = ReallyCalc(a) 
    return CalcSomething._cache[a] 

CalcSomething._cache = { } 

Ésta es la manera más fácil que se me ocurre para la simulación de una variable 'local estática' en Python.
Lo que me molesta es que CalcSomething._cache se menciona fuera de la definición de la función, pero la alternativa sería algo así:

if not hasattr(CalcSomething, "_cache"): 
    setattr(CalcSomething, "_cache", { }) 

dentro de la definición de la función, que es muy engorroso.

¿Hay una manera más elegante?

[EDIT]
Para aclarar, esta pregunta no se trata de cachés de funciones locales, como podría sugerir el ejemplo anterior. He aquí otro ejemplo corto donde un 'estática local' podría ser útil:

def ParseString(s): 
    return ParseString._parser.parse(s) 
# Create a Parser object once, which will be used for all parsings. 
# Assuming a Parser object is heave on resources, for the sake of this example. 
ParseString._parser = Parser() 
+0

BTW, usted mi ght quiere expresar su pregunta de manera diferente. No está buscando una variable estática local, sino una forma de introducir la memoria de su función. –

+0

su segundo ejemplo es peor que el primero. Puede devolver datetime.datetime.strptime (dts, "% m-% d-% Y% H:% M:% S"), ya que strptime es un método de clase que crea un nuevo objeto datetime. Realmente no hay necesidad de crear un objeto datetime. – nosklo

+0

Tiene razón sobre el segundo ejemplo. Cambió eso. –

Respuesta

48

convertirlo en un objeto invocable (ya que es lo que realmente es.)

class CalcSomething(object): 
    def __init__(self): 
     self._cache = {} 
    def __call__(self, a): 
     if a not in self._cache: 
      self._cache[a] = self.reallyCalc(a) 
     return self._cache[a] 
    def reallyCalc(self, a): 
     return # a real answer 
calcSomething = CalcSomething() 

Ahora puede utilizar calcSomething como si se tratara de una función. Pero sigue siendo ordenado y autónomo.

+0

+1: otra solución que oculta la implementación del almacenamiento en caché. Este enfoque no pasó por mi mente, pero se ve más poderoso que mi simple decorador. – Abgan

+0

+1: el uso de la clase * es * la solución. – nosklo

+0

Para aquellos que quieran usar callables personalizados como métodos, tendrás que implementar __get__ de la forma correcta (google para "protocolo de descripción de python" para obtener más información al respecto) –

16

convertirlo en un decorador.

def static_var(var_name, initial_value): 
    def _set_var(obj): 
     setattr(obj, var_name, initial_value) 
     return obj 
    return _set_var 

@static_var("_cache", {}) 
def CalcSomething(a): 
    ... 
+1

¿Realmente considera que es "una forma más elegante"? No, en serio ;-) –

+0

No consideraría las variables estáticas un patrón elegante para usar en Python, pero el decorador al menos encapsula los detalles técnicos de la configuración del atributo. –

+0

+1 para un uso interesante de los atributos de python. @Pau Oyster: no tengo idea de por qué no consideras esto elegante. Si desea una forma no hackish de hacerlo, solo use una clase Calculadora con una variable de instancia. Pidió estática local que es inherentemente fea. –

11

que no escribe decorador que mantendrá caché y su función no será contaminado por el almacenamiento en caché de código:

def cacheResults(aFunc): 
    '''This decorator funcion binds a map between the tuple of arguments 
     and results computed by aFunc for those arguments''' 
    def cachedFunc(*args): 
     if not hasattr(aFunc, '_cache'): 
      aFunc._cache = {} 
     if args in aFunc._cache: 
      return aFunc._cache[args] 
     newVal = aFunc(*args) 
     aFunc._cache[args] = newVal 
     return newVal 
    return cachedFunc 

@cacheResults 
def ReallyCalc(a): 
    '''This function does only actual computation''' 
    return pow(a, 42) 

Tal vez no parece gran cosa al principio, pero se puede utilizar en cualquier lugar que cacheResults() no necesita parámetros de palabra clave. Es posible crear un decorador similar que funcione también para los parámetros de palabras clave, pero eso no pareció ser necesario esta vez.

+0

Aunque este no es el objetivo de mi pregunta (ver la aclaración allí), este es un hermoso esquema para implementar cachés locales. Gracias por eso. –

+0

Puede deshacerse de la condición 'if not hasattr' ... haciendo la variable local' _cache' 'cacheResults'. – GingerPlusPlus

3

Una opción es abusar de los parámetros predeterminados. es decir:

def CalcSomething(a, _cache={}): 
    if _cache.has_key(a): 

Esto tiene la ventaja de que no es necesario para calificar el nombre, y obtendrá rápido acceso local a las variables en lugar de hacer dos búsquedas dict lentos. Sin embargo, todavía tiene el problema de que se menciona fuera de la función (de hecho es peor porque ahora está en la firma de la función).

Para evitar esto, una mejor solución sería envolver la función en un cierre que contenga su estática :

@apply 
def CalcSomething(): 
    cache = {} # statics go here 

    def CalcSomething(a): 
     if cache.has_key(a): 
      return cache[a] 
     cache[a] = ReallyCalc(a) 
     return cache[a] 
    return CalcSomething 
+0

Desafortunadamente, se eliminó la aplicación de Python 3.0. También encontré un puñado de casos similares donde fue breve, simple y útil. –

+0

No creo que sea un buen uso de aplicar. Es mucho más obvio simplemente llamar a la función. –

4

La solución propuesta por S.Lott es la solución que yo propondría también.

No son útiles decoradores "memoize" alrededor, también, como:

Dado todo eso, estoy ofreciendo una alternativa para su intento inicial de una función y un "local estático", que es independiente:

def calc_something(a): 

    try: 
     return calc_something._cache[a] 
    except AttributeError: # _cache is not there 
     calc_something._cache= {} 
    except KeyError: # the result is not there 
     pass 

    # compute result here 

    calc_something._cache[a]= result 
    return result 
Cuestiones relacionadas