Python-任何人都有可以处理不可散列参数的备注修饰符?

问题描述 投票:25回答:5

[我一直在使用以下记忆修饰器(来自很棒的书《 Python Algorithms:掌握Python语言中的基本算法...喜欢它,顺便说一句)。

def memo(func):
    cache = {}
    @ wraps(func)
    def wrap(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrap

此装饰器的问题在于,基于字典的缓存意味着我所有的参数都必须是可哈希的。

有人有没有允许使用不可散列的参数(例如字典)的实现(或对此方法进行了调整)?

我知道缺少哈希值意味着“是否在缓存中?”这一问题。变得不平凡,但我只是想问一下。

===编辑以提供上下文===

我正在研究一个函数,该函数在给定模块字典的情况下返回Parnas样式的“使用层次”:依赖项。这是设置:

def uses_hierarchy(requirements):
    """
    uses_hierarchy(requirements)

    Arguments:
    requirements - a dictionary of the form {mod: list of dependencies, }

    Return value:
    A dictionary of the form {level: list of mods, ...}

    Assumptions:
    - No cyclical requirements (e.g. if a requires b, b cannot require a).
    - Any dependency not listed as a mod assumed to be level 0.

    """

    levels = dict([(mod, _level(mod, requirements))
                   for mod in requirements.iterkeys()])
    reversed = dict([(value, []) for value in levels.itervalues()])
    for k, v in levels.iteritems():
        reversed[v].append(k)
    return reversed


def _level(mod, requirements):
    if not requirements.has_key(mod):
        return 0
    dependencies = requirements[mod]
    if not dependencies:
        return 0
    else:
        return max([_level(dependency, requirements)
                    for dependency in dependencies]) + 1

这样:

>>> requirements = {'a': [],
...                 'b': [],
...                 'c': ['a'],
...                 'd': ['a','b'],
...                 'e': ['c','d'],
...                 'f': ['e']
...                 }

>>> uses_hierarchy(requirements)
{0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}

_ level是我要记住的功能,以使此设置更具可伸缩性。在没有记忆的情况下实现时,它会多次计算依赖级别(例如,我在上面的示例中认为“ a”被计算了8次)。

谢谢,

麦克

python memoization
5个回答
16
投票

这里是Alex Martelli Python Cookbook中的示例,该示例显示了如何使用cPickle创建带有可变参数的函数的备忘录装饰器(原始版本

import cPickle

class MemoizeMutable:
    def __init__(self, fn):
        self.fn = fn
        self.memo = {}
    def __call__(self, *args, **kwds):
        import cPickle
        str = cPickle.dumps(args, 1)+cPickle.dumps(kwds, 1)
        if not self.memo.has_key(str): 
            print "miss"  # DEBUG INFO
            self.memo[str] = self.fn(*args, **kwds)
        else:
            print "hit"  # DEBUG INFO

        return self.memo[str]

这里是link

EDIT:使用您提供的代码和此备注修饰符:

_level = MemoizeMutable(_level)

equirements = {'a': [],
               'b': [],
               'c': ['a'],
               'd': ['a','b'],
               'e': ['c','d'],
               'f': ['e']
                 }

print uses_hierarchy(equirements)

我能够重现此内容:

miss
miss
hit
miss
miss
hit
miss
hit
hit
hit
miss
hit
{0: ['a', 'b'], 1: ['c', 'd'], 2: ['e'], 3: ['f']}

7
投票

从技术上讲,您可以通过将dict(或listset)变成一个元组来解决此问题。例如:

 key = tuple(the_dict.iteritems())
 key = tuple(the_list)
 key = tuple(sorted(the_set))

 cache[key] = func( .. )

但是我不会在memo中执行此操作,我希望更改要在其上使用备忘录的功能-例如,与其接受dict,他们应该只接受(key, value)对,而不是接受列出或设置它们应该只取*args


2
投票

未经严格测试,但似乎可以工作:

from functools import wraps

def memo(func):
    cache = []
    argslist = []
    @ wraps(func)
    def wrap(*args):
        try:
            result = cache[argslist.index(args)]
            print 'cache hit'
            return result
        except ValueError:
            argslist.append(args)
            cache.append(func(*args))
            print 'cache miss'
            return cache[-1]
    return wrap

d1 = { 'a':3, 'b':42 }
d2 = { 'c':7, 'd':19 }
d3 = { 'e':34, 'f':67 }

@memo
def func(d):
    return sum(d.values())

print func(d1)
# cache miss
# 45
print func(d2)
# cache miss
# 26
print func(d3)
# cache miss
# 101
print func(d2)
# cache hit
# 26

2
投票

由于没有其他人提到过它,因此Python Wiki拥有一个装饰器库,其中包含许多memoizing decorator patterns

我个人的偏好是最后一个,它允许调用代码将方法简单地视为延迟评估的属性,而不是方法。但我更喜欢here的实现。

class lazy_property(object):
    '''Decorator: Enables the value of a property to be lazy-loaded.
    From Mercurial's util.propertycache

    Apply this decorator to a no-argument method of a class and you
    will be able to access the result as a lazy-loaded class property.
    The method becomes inaccessible, and the property isn't loaded
    until the first time it's called.  Repeated calls to the property
    don't re-run the function.

    This takes advantage of the override behavior of Descriptors - 
    __get__ is only called if an attribute with the same name does
    not exist.  By not setting __set__ this is a non-data descriptor,
    and "If an instance's dictionary has an entry with the same name
    as a non-data descriptor, the dictionary entry takes precedence."
     - http://users.rcn.com/python/download/Descriptor.htm

    To trigger a re-computation, 'del' the property - the value, not
    this class, will be deleted, and the value will be restored upon
    the next attempt to access the property.
    '''
    def __init__(self,func):
        self.func = func
        self.name = func.__name__
    def __get__(self, obj, type=None):
        result = self.func(obj)
        setattr(obj, self.name, result)
        return result

在上面链接的同一文件中,还有一个lazy_dict装饰器,使您可以将函数视为具有延迟计算值的字典。


0
投票

我已经搜索并找到了一个不错的python包。

https://pypi.org/project/memoization/

  • 易于使用
  • 可能使用不可散列的参数
  • 其他有用的选项(生存时间)
© www.soinside.com 2019 - 2024. All rights reserved.