2012-03-24 18 views
5

Estoy creando algunos objetos a partir de archivos (validadores de archivos xsd de plantillas, para unir otros archivos xsd, como ocurre), y me gustaría recrear los objetos cuando el archivo en el disco cambia.Caché de archivos Python

que podría crear algo así como:

def getobj(fname, cache = {}): 
    try: 
     obj, lastloaded = cache[fname] 
     if lastloaded < last_time_written(fname): 
      # same stuff as in except clause 
    except KeyError: 
     obj = create_from_file(fname) 
     cache[fname] = (obj, currenttime) 

    return obj 

Sin embargo, yo preferiría usar algún otro código de analizar si es que existe. ¿Hay una biblioteca existente que hace algo como esto?

Actualización: Estoy usando python 2.7.1.

+0

Tenga en cuenta que en vez de repetir el código en la cláusula '' except' dentro de su declaración if', sólo podría aumentar 'KeyError()' en lugar. – Amber

+2

¡Buen argumento predeterminado mutable! – katrielalex

+0

@katrielalex ¡Gracias! – Marcin

Respuesta

3

Su código (incluida la lógica de caché) se ve bien.

Considere mover la variable de memoria fuera de la definición de la función. Eso permitirá agregar otras funciones para borrar o inspeccionar el caché.

Si desea buscar en el código que hace algo similar, un vistazo a la fuente para el módulo filecmp: http://hg.python.org/cpython/file/2.7/Lib/filecmp.py La parte interesante es cómo se utiliza el stat module para determinar si un archivo ha cambiado. Aquí está la firma función:

def _sig(st): 
    return (stat.S_IFMT(st.st_mode), 
      st.st_size, 
      st.st_mtime) 
1

A menos que haya una razón específica para utilizarlo como argumento me gustaría utilizar la caché como un objeto global

+0

Válido, era más un acto de fantasía mientras componía en la ventana SO. – Marcin

+1

bueno, una razón es para el rendimiento. el objetivo de un caché es mejorar el rendimiento, y las búsquedas de variables locales (incluidos los argumentos predeterminados) son más rápidas en una cantidad modesta en comparación con las búsquedas globales. Dicho esto, este patrón es una excelente manera de hacer tropezar a las futuras generaciones que aún no están familiarizadas con esta peculiaridad del lenguaje, y como dices, un * * debe ser preferido por su claridad cuando el rendimiento no es de * suma importancia * . – SingleNegationElimination

+1

@TokenMacGuy la expresión habitual es 'def foo (cache = cache):' para copiar variables globales en el ámbito local. – katrielalex

1

Tres pensamientos.

  1. Utilice try... except... else para obtener un flujo de control más limpio.

  2. Los tiempos de modificación de archivos son notoriamente inestables, en particular, ¡no corresponden necesariamente a la última vez que se modificó el archivo!

  3. Python 3 contiene un decorador de almacenamiento en caché: functools.lru_cache. Aquí está la fuente.

    def lru_cache(maxsize=100): 
        """Least-recently-used cache decorator. 
    
        If *maxsize* is set to None, the LRU features are disabled and the cache 
        can grow without bound. 
    
        Arguments to the cached function must be hashable. 
    
        View the cache statistics named tuple (hits, misses, maxsize, currsize) with 
        f.cache_info(). Clear the cache and statistics with f.cache_clear(). 
        Access the underlying function with f.__wrapped__. 
    
        See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used 
    
        """ 
        # Users should only access the lru_cache through its public API: 
        #  cache_info, cache_clear, and f.__wrapped__ 
        # The internals of the lru_cache are encapsulated for thread safety and 
        # to allow the implementation to change (including a possible C version). 
    
        def decorating_function(user_function, 
           tuple=tuple, sorted=sorted, len=len, KeyError=KeyError): 
    
         hits = misses = 0 
         kwd_mark = (object(),)   # separates positional and keyword args 
         lock = Lock()     # needed because ordereddicts aren't threadsafe 
    
         if maxsize is None: 
          cache = dict()    # simple cache without ordering or size limit 
    
          @wraps(user_function) 
          def wrapper(*args, **kwds): 
           nonlocal hits, misses 
           key = args 
           if kwds: 
            key += kwd_mark + tuple(sorted(kwds.items())) 
           try: 
            result = cache[key] 
            hits += 1 
           except KeyError: 
            result = user_function(*args, **kwds) 
            cache[key] = result 
            misses += 1 
           return result 
         else: 
          cache = OrderedDict()  # ordered least recent to most recent 
          cache_popitem = cache.popitem 
          cache_renew = cache.move_to_end 
    
          @wraps(user_function) 
          def wrapper(*args, **kwds): 
           nonlocal hits, misses 
           key = args 
           if kwds: 
            key += kwd_mark + tuple(sorted(kwds.items())) 
           try: 
            with lock: 
             result = cache[key] 
             cache_renew(key)  # record recent use of this key 
             hits += 1 
           except KeyError: 
            result = user_function(*args, **kwds) 
            with lock: 
             cache[key] = result  # record recent use of this key 
             misses += 1 
             if len(cache) > maxsize: 
              cache_popitem(0) # purge least recently used cache entry 
           return result 
    
         def cache_info(): 
          """Report cache statistics""" 
          with lock: 
           return _CacheInfo(hits, misses, maxsize, len(cache)) 
    
         def cache_clear(): 
          """Clear the cache and cache statistics""" 
          nonlocal hits, misses 
          with lock: 
           cache.clear() 
           hits = misses = 0 
    
         wrapper.cache_info = cache_info 
         wrapper.cache_clear = cache_clear 
         return wrapper 
    
        return decorating_function 
    
+0

Nunca supe sobre la cláusula 'else'. Gracias por eso (y todo esto). – Marcin

Cuestiones relacionadas