2014-07-24 7 views
6

Ich versuche, eine Memoization-Bibliothek zu schreiben, die shelve verwendet, um die Rückgabewerte persistent zu speichern. Wenn ich Memo-Funktionen habe, die andere Memo-Funktionen aufrufen, frage ich mich, wie ich die Shelf-Datei richtig öffne.Kann Pythons shelve.open verschachtelt aufgerufen werden?

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

Außer dies nicht funktioniert,

$ 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 

Was Sie für eine Lösung dieser Art von Problem zu empfehlen.

+6

Ich denke, Sie sollten nicht zwei offene Schreibzeiger auf die gleiche Datei haben .. das wird fast sicher zu unerwünschtem Verhalten führen ... verwenden Sie stattdessen 'file.seek (0)', wenn Sie zurück zum Anfang von gehen wollen eine offene Datei –

+0

OK, macht Sinn, aber ich möchte nicht wirklich zum Anfang von irgendwelchen Dateien zurückkehren. Ich möchte grundsätzlich zum zweiten 'open' greifen, um die bereits geöffnete Datei des ersten zu verwenden, wenn sie schon geöffnet wurde, wenn nicht, dann öffne sie. –

+0

Offensichtlich immer noch geöffnet, da Sie immer noch in seinem Kontextblock sind, es sei denn, Sie haben es explizit irgendwo geschlossen –

Antwort

2

Anstatt zu nisten versuchen, Anrufe zu öffnen (was, wie Sie entdeckt haben, nicht funktioniert), könnten Sie Ihre Dekorateur einen Verweis auf den Griff nach shelve.open, und dann, wenn es wieder machen pflegen existiert und ist nach wie vor offen, wiederverwenden, dass für nachfolgende Aufrufe:

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

Ausgang:

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:

Sie auch den Dekorateur mit einem WeakValueDictionary implementieren könnte, was etwas besser lesbar aussieht:

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 

Sobald es keine weiteren Hinweise auf einen Griff, wird es gelöscht aus dem Wörterbuch. Da unser Handle nur den Rahmen verlässt, wenn der äußerste Aufruf einer verzierten Funktion endet, haben wir immer einen Eintrag im Dict, während ein Handle offen ist, und keinen Eintrag direkt nach dem Schließen.

+1

Ist dies nicht 'mit shelve.open (Dateiname, Writeback = True) als c:' Schließen Sie das Regal nach diesem Block? In diesem Fall wird es das nächste Mal nicht geöffnet sein? –

+0

@ saul.shanabrook Ja, aber der Dekorateur überprüft das. mit 'not hasattr (getattr (cache, handle_name) .dict," closed ")' Teil der 'if'-Anweisung. 'Cache. .dict' hat nur dann ein 'closed' Attribut, wenn das Handle geschlossen ist. Wenn wir es finden, öffnen wir den Griff wieder. – dano

+0

@ saul.shanabrook Auch ich habe gerade meine Antwort bearbeitet, so dass der Dekorator die Verwendung von Haltepunkten für mehrere Cache-Dateien unterstützt. Und ich habe den Ausgabebereich aktualisiert, um die Ausgabe widerzuspiegeln, wenn der Cache noch nicht existiert. – dano

-1

Sie öffnen die Datei zweimal, schließen sie aber nie, um die Datei für den jeweiligen Zweck zu aktualisieren. Verwenden Sie f.close() am Ende.

+0

Ich habe es nur angeschaut. Du aktualisierst nicht vom ersten Teil, also gibt es nirgends für das "da" zu gehen, da es nicht existiert. Es ist wie das Öffnen von zwei Fenstern – user3874017

4

Nein, Sie haben möglicherweise keine shelve Instanzen mit demselben Dateinamen verschachtelt.

Das Shelve-Modul unterstützt nicht den gleichzeitigen Lese-/Schreibzugriff auf zurückgestellte Objekte. (Mehrere gleichzeitige Lesezugriffe sind sicher.) Wenn ein Programm zum Schreiben geöffnet ist, sollte es kein anderes Programm zum Lesen oder Schreiben öffnen. Die Unix-Dateisperrung kann verwendet werden, um dies zu lösen, aber dies unterscheidet sich zwischen Unix-Versionen und erfordert Kenntnisse über die verwendete Datenbankimplementierung.

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

Verwandte Themen