我还没有看到一种既定的方法来记忆一个带有关键字参数的函数,即类型
def f(*args, **kwargs)
因为通常备忘录有dict
来缓存给定输入参数集的结果,而kwargs
是dict
因此不可用。我已尝试使用
(args, frozenset(kwargs.items()))
作为缓存dict
的关键,但仅当kwargs
中的值可以清除时才有效。此外,正如以下答案中所指出的,frozenset
不是有序数据结构。因此,这个解决方案可能更安全:
(args, tuple(sorted(kwargs.items())))
但它仍然无法应对不可清除的元素。我看到的另一种方法是在缓存键中使用string
的{{1}}表示:
kwargs
我看到的唯一缺点是散列可能非常长的字符串的开销。据我所知,结果应该是正确的。有人能发现后一种方法有什么问题吗?下面的答案之一指出,这假定(args, str(sorted(kwargs.items())))
或__str__
函数的某些行为用于关键字参数的值。这似乎是一个表演者。
是否有另一种更成熟的实现备忘的方法可以应对__repr__
和不可清除的参数值?
答案 0 :(得分:11)
key = (args, frozenset(kwargs.items())
这是您在不对数据做出假设的情况下可以做到的“最佳”。
然而,想要在字典上执行memoization似乎是可以想象的(尽管有点不寻常),如果你需要它,你可以特殊情况。例如,您可以在复制词典时递归应用frozenset(---.items())
。
如果你sorted
,那么你可能会遇到无法解决问题的糟糕情况。例如,“子集和相等比较不会推广到完整的排序函数。例如,任何两个不相交的集合都不相等而且不是彼此的子集,因此以下所有都返回False:ab。 ,集合不实现 cmp ()方法。“
>>> sorted([frozenset({1,2}), frozenset({1,3})])
[frozenset({1, 2}), frozenset({1, 3})]
>>> sorted([frozenset({1,3}), frozenset({1,2})]) # THE SAME
[frozenset({1, 3}), frozenset({1, 2})] # DIFFERENT SORT RESULT
# sorted(stuff) != sorted(reversed(stuff)), if not strictly totally ordered
编辑:Ignacio说:“虽然你不能在任意dicts上使用sorted(),但kwargs会有str键。”这是完全正确的。因此,这对于密钥来说不是问题,但如果您(或不太可能的代表)依赖于某种方式的排序,可能需要记住值。
关于使用str
:
大多数数据可以很好地运行,但是攻击者(例如在安全漏洞上下文中)可能会发生冲突。要记住这一点并不容易,因为大多数默认repr
都使用了大量优秀的分组和转义。事实上,我无法找到这样的碰撞。但是可以使用草率的第三方或不完整的repr
实现。
另请考虑以下事项:如果您要存储((<map object at 0x1377d50>,), frozenset(...))
和((<list_iterator object at 0x1377dd0>,<list_iterator object at 0x1377dd0>), frozenset(...))
等密钥,则只需调用相同的项目,您的缓存就会无限增长。 (你可以使用正则表达式解决这个问题......)并且尝试使用生成器会搞乱你正在使用的函数的语义。如果你想记住is
- 风格平等而不是==
- 风格平等,这可能是理想的行为。
在解释器中执行类似str({1:object()})
的操作,每次都会在内存中的同一位置返回一个对象!我认为这是工作中的垃圾收集器。这将是灾难性的,因为如果您碰巧是哈希<some object at 0x???????>
并且您碰巧在稍后的同一内存位置创建了相同类型的对象(由于垃圾回收),您将从memoized获得不正确的结果功能。如上所述,一个可能非常糟糕的解决方法是使用正则表达式检测此类对象。
答案 1 :(得分:7)
dicts可以按任意顺序排列,因此无法保证后者可以正常工作。使用sorted(kwargs.items())
首先按键排序。
答案 2 :(得分:4)
这与EMS所说的类似,但最好的方法是:
key = cPickle.dumps((*args, **kwargs))
我一直在用装饰器进行大量的记忆研究和测试,这是迄今为止我发现的最佳方法。
答案 3 :(得分:1)
下面:
from functools import wraps
def memoize(fun):
"""A simple memoize decorator for functions supporting positional args."""
@wraps(fun)
def wrapper(*args, **kwargs):
key = (args, frozenset(sorted(kwargs.items())))
try:
return cache[key]
except KeyError:
ret = cache[key] = fun(*args, **kwargs)
return ret
cache = {}
return wrapper
试验:
import unittest
class TestMemoize(unittest.TestCase):
def test_it(self):
@memoize
def foo(*args, **kwargs):
"foo docstring"
calls.append(None)
return (args, kwargs)
calls = []
# no args
for x in range(2):
ret = foo()
expected = ((), {})
self.assertEqual(ret, expected)
self.assertEqual(len(calls), 1)
# with args
for x in range(2):
ret = foo(1)
expected = ((1, ), {})
self.assertEqual(ret, expected)
self.assertEqual(len(calls), 2)
# with args + kwargs
for x in range(2):
ret = foo(1, bar=2)
expected = ((1, ), {'bar': 2})
self.assertEqual(ret, expected)
self.assertEqual(len(calls), 3)
self.assertEqual(foo.__doc__, "foo docstring")
unittest.main()
只要您不传递不可用类型(例如dict)作为参数,这就可以工作。 我没有这方面的解决方案,但collections.lru_cache()实现可能有。 请在此处查看_make_key()函数: http://code.activestate.com/recipes/578078/
答案 4 :(得分:0)
key = pickle.dumps( (args, sorted(kwargs.items()), -1 )
怎么样?
这似乎是比str()或repr()更强大的方法。