2014-07-24 24 views
6

Sto provando a scrivere una libreria di memoization che utilizza shelve per memorizzare i valori di ritorno in modo persistente. Se ho memorizzato funzioni che chiamano altre funzioni memorizzate, mi chiedo come aprire correttamente il file shelf.È possibile chiamare shelve.open Python in modo annidato?

import shelve 
import functools 


def cache(filename): 
    def decorating_function(user_function): 
     def wrapper(*args, **kwds): 
      key = str(hash(functools._make_key(args, kwds, typed=False))) 
      with shelve.open(filename, writeback=True) as cache: 
       if key in cache: 
        return cache[key] 
       else: 
        result = user_function(*args, **kwds) 
        cache[key] = result 
        return result 

     return functools.update_wrapper(wrapper, user_function) 

    return decorating_function 


@cache(filename='cache') 
def expensive_calculation(): 
    print('inside function') 
    return 


@cache(filename='cache') 
def other_expensive_calculation(): 
    print('outside function') 
    return expensive_calculation() 

other_expensive_calculation() 

Solo che questa non funziona

$ python3 shelve_test.py 
outside function 
Traceback (most recent call last): 
    File "shelve_test.py", line 33, in <module> 
    other_expensive_calculation() 
    File "shelve_test.py", line 13, in wrapper 
    result = user_function(*args, **kwds) 
    File "shelve_test.py", line 31, in other_expensive_calculation 
    return expensive_calculation() 
    File "shelve_test.py", line 9, in wrapper 
    with shelve.open(filename, writeback=True) as cache: 
    File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/shelve.py", line 239, in open 
    return DbfilenameShelf(filename, flag, protocol, writeback) 
    File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/shelve.py", line 223, in __init__ 
    Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback) 
    File "/usr/local/Cellar/python3/3.4.1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/dbm/__init__.py", line 94, in open 
    return mod.open(file, flag, mode) 
_gdbm.error: [Errno 35] Resource temporarily unavailable 

Cosa consigliate per una soluzione a questo tipo di problema.

+6

penso che non si dovrebbero avere due puntatori di scrittura aperte allo stesso file .. che quasi certamente porterà ad un comportamento indesiderato ... invece usare 'file.seek (0)', se si vuole tornare all'inizio del un file aperto –

+0

OK, ha senso, ma non voglio tornare all'inizio di alcun file. Io fondamentalmente voglio il secondo 'open' utilizzare il file già aperto del primo, se è già stato aperto, se non poi aprirlo. –

+0

la sua, ovviamente ancora aperta dal momento che sono ancora all'interno del suo blocco contesto a meno che non esplicitamente chiuso da qualche parte –

risposta

2

Piuttosto che cercare di nido chiamate ad aprire (che, come si è scoperto, non funziona), si potrebbe rendere il vostro decoratore mantenere un riferimento al manico restituito da shelve.open, e poi se esiste ed è ancora aperto, ri-uso che per chiamate successive:

import shelve 
import functools 

def _check_cache(cache_, key, func, args, kwargs): 
    if key in cache_: 
     print("Using cached results") 
     return cache_[key] 
    else: 
     print("No cached results, calling function") 
     result = func(*args, **kwargs) 
     cache_[key] = result 
     return result 

def cache(filename): 
    def decorating_function(user_function): 
     def wrapper(*args, **kwds): 
      args_key = str(hash(functools._make_key(args, kwds, typed=False))) 
      func_key = '.'.join([user_function.__module__, user_function.__name__]) 
      key = func_key + args_key 
      handle_name = "{}_handle".format(filename) 
      if (hasattr(cache, handle_name) and 
       not hasattr(getattr(cache, handle_name).dict, "closed") 
       ): 
       print("Using open handle") 
       return _check_cache(getattr(cache, handle_name), key, 
            user_function, args, kwds) 
      else: 
       print("Opening handle") 
       with shelve.open(filename, writeback=True) as c: 
        setattr(cache, handle_name, c) # Save a reference to the open handle 
        return _check_cache(c, key, user_function, args, kwds) 

     return functools.update_wrapper(wrapper, user_function) 
    return decorating_function 


@cache(filename='cache') 
def expensive_calculation(): 
    print('inside function') 
    return 


@cache(filename='cache') 
def other_expensive_calculation(): 
    print('outside function') 
    return expensive_calculation() 

other_expensive_calculation() 
print("Again") 
other_expensive_calculation() 

uscita:

Opening handle 
No cached results, calling function 
outside function 
Using open handle 
No cached results, calling function 
inside function 
Again 
Opening handle 
Using cached results 

Edit:

Si potrebbe anche implementare il decoratore con un WeakValueDictionary, che sembra un po 'più leggibile:

from weakref import WeakValueDictionary 

_handle_dict = WeakValueDictionary() 
def cache(filename): 
    def decorating_function(user_function): 
     def wrapper(*args, **kwds): 
      args_key = str(hash(functools._make_key(args, kwds, typed=False))) 
      func_key = '.'.join([user_function.__module__, user_function.__name__]) 
      key = func_key + args_key 
      handle_name = "{}_handle".format(filename) 
      if handle_name in _handle_dict: 
       print("Using open handle") 
       return _check_cache(_handle_dict[handle_name], key, 
            user_function, args, kwds) 
      else: 
       print("Opening handle") 
       with shelve.open(filename, writeback=True) as c: 
        _handle_dict[handle_name] = c 
        return _check_cache(c, key, user_function, args, kwds) 

     return functools.update_wrapper(wrapper, user_function) 
    return decorating_function 

Appena non ci sono altri riferimenti a una maniglia, verrà eliminato dal dizionario Dal momento che il nostro handle esce dall'ambito solo quando termina la chiamata più esterna a una funzione decorata, avremo sempre una voce nel dict mentre una maniglia è aperta e nessuna voce subito dopo la chiusura.

+1

Questo non '' con shelve.open (nomefile, writeback = True) come c: 'chiudi lo scaffale dopo quel blocco? In tal caso non sarà aperto la prossima volta? –

+0

@ saul.shanabrook Sì, ma il decoratore lo controlla. con 'not hasattr (getattr (cache, handle_name) .dict," closed ")' parte dell'istruzione 'if'. 'Cache. .dict' avrà un attributo 'closed' solo se l'handle è chiuso. Se lo troviamo, apriamo nuovamente la maniglia. – dano

+0

@ saul.shanabrook Ho anche modificato la mia risposta in modo che il decoratore supporti l'uso di maniglie di mantenimento per più file di cache. E ho aggiornato la sezione di output per riflettere l'output quando la cache non esiste già. – dano

-1

Si sta aprendo il file due volte ma in realtà non lo si chiude per aggiornare il file per qualsiasi utilizzo. Utilizzare f.close() alla fine.

+0

Basta guardarlo. Non stai aggiornando dalla prima parte, quindi non c'è da nessuna parte il "là" per andare come non esiste. E 'come aprire due finestre di questo – user3874017

4

No, è possibile che non siano nidificate le istanze shelve con lo stesso nome file.

Il modulo shelve non supporta l'accesso simultaneo in lettura/scrittura agli oggetti shelved. (Gli accessi multipli simultanei in lettura sono sicuri). Quando un programma ha uno scaffale aperto per la scrittura, nessun altro programma dovrebbe averlo aperto per la lettura o la scrittura. Il blocco dei file Unix può essere utilizzato per risolvere questo problema, ma questo differisce tra le versioni di Unix e richiede conoscenze sull'implementazione del database utilizzata.

https://docs.python.org/3/library/shelve.html#restrictions