【问题标题】:Can Python's shelve.open be called in a nested fashion?Python 的 shelve.open 可以嵌套调用吗?
【发布时间】:2014-09-16 08:16:03
【问题描述】:

我正在尝试编写一个使用shelve 持久存储返回值的记忆库。如果我有调用其他 memoized 函数的 memoized 函数,我想知道如何正确打开架子文件。

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

除非这不起作用

$ 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

您对此类问题的解决方案有何建议。

【问题讨论】:

  • 我认为你不应该有两个指向同一个文件的开放写入指针。这几乎肯定会导致不良行为......如果你想回到开头,请使用file.seek(0)一个打开的文件
  • 好的,有道理,但我真的不想回到任何文件的开头。我基本上是想让第二个open使用第一个已经打开的文件,如果它已经打开,如果没有则打开它。
  • 它显然仍处于打开状态,因为您仍在其上下文块中,除非您在某处明确将其关闭
  • @dano 再次更新,带有工作(除了不是)示例
  • 鉴于您更新的示例,真正的问题不是“open 是否可以嵌套调用?”,而是“shelve.open以嵌套方式调用?”。

标签: python file-io memoization shelve


【解决方案1】:

不,您可能没有嵌套具有相同文件名的 shelve 实例。

搁置模块不支持对搁置对象的并发读/写访问。 (多个同时读取访问是安全的。)当一个程序有一个架子可供写入时,任何其他程序都不应该打开它来读取或写入。可以使用 Unix 文件锁定来解决这个问题,但这在 Unix 版本之间有所不同,并且需要了解所使用的数据库实现。

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

【讨论】:

    【解决方案2】:

    而不是尝试嵌套调用 open (正如您所发现的那样,它不起作用),您可以让您的装饰器维护对 shelve.open 返回的句柄的引用,然后如果它存在并且仍然打开,将其重新用于后续调用:

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

    输出:

    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
    

    编辑:

    您也可以使用WeakValueDictionary 来实现装饰器,这样看起来更易读:

    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
    

    一旦没有其他对句柄的引用,它将从字典中删除。由于我们的句柄仅在对装饰函数的最外层调用结束时才超出范围,因此当句柄打开时,我们将始终在 dict 中有一个条目,而在它关闭后则没有条目。

    【讨论】:

    • 这不是with shelve.open(filename, writeback=True) as c: 在该块之后关闭架子吗?什么情况下下次就打不开了?
    • @saul.shanabrook 是的,但是装饰器会检查它。与if 语句的not hasattr(getattr(cache, handle_name).dict, "closed") 部分一起使用。如果句柄关闭,cache.&lt;handle name&gt;.dict 将仅具有 closed 属性。如果我们找到它,我们再次打开手柄。
    • @saul.shanabrook 另外,我刚刚编辑了我的答案,以便装饰器支持对多个缓存文件使用保留句柄。当缓存不存在时,我更新了输出部分以反映输出。
    • 运行此脚本时出现错误:gist.github.com/saulshanabrook/e5000eebeffde91c7453
    • 是的,更新版本有效。非常感谢!这是完美的
    【解决方案3】:

    您打开文件两次,但从未真正关闭它以更新文件以用于任何用途。最后使用f.close ()

    【讨论】:

    • 刚刚看了一遍。您没有从第一部分更新,因此“那里”无处可去,因为它不存在byet。就像打开它的两个窗口
    猜你喜欢
    • 2011-11-26
    • 1970-01-01
    • 2012-03-16
    • 2011-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    相关资源
    最近更新 更多