2012-03-24 21 views
5

Tworzę obiekty z plików (weryfikatory z plików xsd szablonów, aby narysować razem inne pliki xsd, tak jak to się dzieje) i chciałbym odtworzyć obiekty, gdy plik na dysku ulegnie zmianie.Pamięć podręczna plików w języku Python

mógłbym stworzyć coś takiego:

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 

Chciałbym jednak wolą korzystać ktoś inny jest przetestowany kod, jeśli taka istnieje. Czy istnieje istniejąca biblioteka, która robi coś takiego?

Aktualizacja: Używam Pythona 2.7.1.

+0

Zauważ, że zamiast powtarzania kodu w 'except' klauzuli wewnątrz swojej' if' oświadczeniu, może po prostu 'podnieść KeyError()' zamiast. – Amber

+2

Miły zmienny argument domyślny! – katrielalex

+0

@katrielalex Dziękujemy! – Marcin

Odpowiedz

3

Twój kod (w tym logika pamięci podręcznej) wygląda dobrze.

Należy rozważyć przeniesienie wartości zmiennej pamięci podręcznej poza definicję funkcji. Umożliwi to dodanie innych funkcji do czyszczenia lub przeglądania pamięci podręcznej.

Jeśli chcesz przejrzeć kod, który robi coś podobnego, spójrz na źródło dla modułu filecmp: http://hg.python.org/cpython/file/2.7/Lib/filecmp.py. Interesującą częścią jest to, w jaki sposób stat module jest używany do określenia, czy plik się zmienił. Oto podpis funkcja:

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

ile nie ma konkretnego powodu, aby używać go jako argumentu użyłbym cache jako globalnego obiektu

+0

Ważne, to było bardziej akt kaprysu podczas komponowania w oknie SO. – Marcin

+1

cóż, jednym z powodów jest wydajność. Głównym celem pamięci podręcznej jest poprawa wydajności, a lokalne zmienne zmiennych (w tym domyślne argumenty) są szybsze o skromniejszą sumę w porównaniu do globalnych wyszukiwań. To powiedziawszy, ten wzór jest świetnym sposobem na potknięcie się o przyszłe pokolenia, które nie są już zaznajomione z tym dziwactwem językowym, a jak mówisz, globalny * powinien * być preferowany ze względu na jego jednoznaczność, gdy wydajność nie jest * najważniejsza * znaczenie . – SingleNegationElimination

+1

@TokenMacGuy zwykłym idiomem jest 'def foo (cache = cache):' do kopiowania zmiennych globalnych do zakresu lokalnego. – katrielalex

1

Trzy myśli.

  1. Użyj try... except... else dla lepszego przepływu sterowania.

  2. Czasy modyfikacji plików są notorycznie niestabilne - w szczególności niekoniecznie odpowiadają najnowszemu czasowi modyfikacji pliku!

  3. Python 3 zawiera dekorator buforowania: functools.lru_cache. Oto źródło.

    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

Nigdy nie wiedziałem o klauzuli "else". Dzięki za to (i wszystko to). – Marcin

Powiązane problemy