【问题标题】:How to memoize **kwargs?如何记忆 **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 和不可散列的参数值?

【问题讨论】:

    标签: python memoization


    【解决方案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())) 有效。
          猜你喜欢
          • 1970-01-01
          • 2018-06-25
          • 2019-10-02
          • 1970-01-01
          • 2017-06-25
          • 2018-01-11
          • 2015-08-03
          • 2014-10-31
          • 2019-03-26
          相关资源
          最近更新 更多