如何记忆 **kwargs?

Posted

技术标签:

【中文标题】如何记忆 **kwargs?【英文标题】:How to memoize **kwargs? 【发布时间】:2011-09-18 11:26:12 【问题描述】:

我还没有看到一种既定的方法来记忆一个接受关键字参数的函数,即某种类型的东西

def f(*args, **kwargs)

因为通常记忆器有一个dict 来缓存给定输入参数集的结果,而kwargs 是一个dict,因此是不可散列的。我已经尝试过,在here 的讨论之后,使用

(args, frozenset(kwargs.items()))

作为缓存 dict 的键,但这仅适用于 kwargs 中的值是可散列的。此外,正如下面的答案所指出的,frozenset 不是有序数据结构。因此,此解决方案可能更安全:

(args, tuple(sorted(kwargs.items())))

但它仍然无法处理不可散列的元素。我见过的另一种方法是在缓存键中使用kwargsstring 表示:

(args, str(sorted(kwargs.items())))

我看到的唯一缺点是散列可能很长的字符串的开销。据我所见,结果应该是正确的。谁能发现后一种方法的任何问题?下面的答案之一指出,这假设了 __str____repr__ 函数对于关键字参数的值的某些行为。这似乎是一场表演。

是否有另一种更成熟的方式来实现记忆化,可以处理**kwargs 和不可散列的参数值?

【问题讨论】:

【参考方案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/

【讨论】:

【参考方案2】:

跟EMS说的差不多,不过最好的办法是:

key = cPickle.dumps((*args, **kwargs))

我一直在对装饰器的记忆进行大量研究和测试,这是迄今为止我发现的最好的方法。

【讨论】:

【参考方案3】:

key = pickle.dumps( (args, sorted(kwargs.items()), -1 ) 呢? 这似乎是一种比 str() 或 repr() 更稳健的方法。

【讨论】:

【参考方案4】:
key = (args, frozenset(kwargs.items())

这是您无需对数据做出假设即可做到的“最佳”。

然而,想要对字典进行记忆似乎是可以想象的(虽然有点不寻常),如果你愿意的话,你可以特殊情况。例如,您可以在复制字典时递归地应用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

大多数数据会很好地工作,但对手(例如在安全漏洞环境中)可能会制造冲突。介意你并不容易,因为大多数默认的reprs 使用了很多好的分组和转义。事实上,我找不到这样的碰撞。但如果第三方草率或不完整的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 很好的一点,第二种方法是受数据的__str____repr__ 方法的支配。 您有没有感觉frozenset(kwargs.items()) 的排序对于同一平台上的同一kwargs 是否具有确定性? 虽然你不能在任意dicts上使用sorted(),但kwargs会有str键。 @juanchopanza: setfrozenset 没有排序 确实如此,所以这可能排除了第一个选项。但也许是 tuple 排序后的 kwargs.items()...【参考方案5】:

dicts 可以按任意顺序排列,因此不能保证后者会起作用。使用sorted(kwargs.items())先按键排序。

【讨论】:

+1 非常好!我会纠正这个问题,因为这不是重点。 顺便说一句,这会使使用frozenset的第一种方法无效吗? 我不知道frozenset的合同细节,但我相信它可以。 Memoization 通常使用 args 作为带有结果的 dict 的键,hash(sorted('key': 'arg'.items())) 失败但 hash(frozenset('key': 'arg'.items())) 有效。

以上是关于如何记忆 **kwargs?的主要内容,如果未能解决你的问题,请参考以下文章

Python中*args 和**kwargs的用法

记忆装饰器保持存储的值

在 Django REST Framework 中,如何在 html 模板中访问 kwargs 上下文? view.kwargs.foo 对我不起作用

如何检查 **kwargs 中的键是不是存在?

如何将 kwargs 传递给 boost-python 包装函数?

如何将定义的字典传递给 Python 中的 **kwargs?