Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/365.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何记忆**kwargs?_Python_Memoization - Fatal编程技术网

Python 如何记忆**kwargs?

Python 如何记忆**kwargs?,python,memoization,Python,Memoization,我还没有看到一种既定的方法来记忆一个函数,它采用关键字参数,即某种类型的参数 def f(*args, **kwargs) 因为通常一个记忆器有一个dict来缓存一组给定输入参数的结果,而kwargs是一个dict,因此不可损坏。在讨论之后,我尝试使用 作为缓存dict的键,但这仅在kwargs中的值可散列时起作用。此外,正如下面的答案所指出的,frozenset不是一个有序的数据结构。因此,此解决方案可能更安全: (args, tuple(sorted(kwargs.items())))

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

def f(*args, **kwargs)
因为通常一个记忆器有一个
dict
来缓存一组给定输入参数的结果,而
kwargs
是一个
dict
,因此不可损坏。在讨论之后,我尝试使用

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

(args, tuple(sorted(kwargs.items())))
但它仍然无法处理不可散列的元素。我见过的另一种方法是在缓存键中使用
kwargs
string
表示:

(args, str(sorted(kwargs.items())))
我看到的唯一缺点是对可能非常长的字符串进行散列的开销。据我所知,结果应该是正确的。有人能发现后一种方法有什么问题吗?下面的一个答案指出,对于关键字参数的值,这假定
\uuu str\uuuu
\uuuu repr\uuuu
函数具有某些行为。这看起来像是一场表演


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

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

key = (args, frozenset(kwargs.items())
这是您在不对数据进行假设的情况下所能做到的“最佳”结果

然而,想要在字典上执行回忆录似乎是可以想象的(尽管有点不寻常),如果您愿意,您可以使用特殊情况。例如,您可以在复制字典时递归地应用
frozenset(---.items())


如果您进行了排序,则可能会遇到无法排序键的糟糕情况。例如,“子集和相等比较不能推广到一个完整的排序函数。例如,任何两个不相交的集合都不相等,也不是彼此的子集,因此以下所有集合都返回False:ab。因此,集合不实现cmp()方法。”

edit:Ignacio说“虽然不能在任意dict上使用sorted(),但kwargs将具有str键。”这是完全正确的。因此,这不是键的问题,但如果您(或不太可能的repr)以某种方式依赖排序,则可能需要记住值的问题


关于使用
str

在这种情况下,大多数数据都能很好地工作,但对手(例如,在安全漏洞环境中)有可能制造冲突。记住这并不容易,因为大多数默认的
repr
s都使用了很多好的分组和转义。事实上,我没能找到这样的碰撞。但是,如果第三方的实现马虎或不完整的
repr
实现,这是可能的


还考虑以下内容:如果您正在存储密钥,如<代码>((,),FROZSENSET(…))<代码> > <代码>((,),FROZSESET(…))< /代码>,您的缓存将仅通过调用相同的项而无限增长。(您也许可以使用regex解决这个问题…)并且尝试使用生成器会弄乱您正在使用的函数的语义。这可能是您想要的行为,但如果您希望在

上记录为
-样式相等,而不是
=
-样式相等


在解释器中执行类似于
str({1:object()})
的操作,每次都会在内存中的相同位置返回一个对象!我想这是工作中的垃圾收集器。这将是灾难性的,因为如果您碰巧正在散列
,并且您碰巧稍后在同一内存位置创建了相同类型的对象(由于垃圾收集),那么您将从memorized函数中得到不正确的结果。如前所述,一个可能真正的黑客解决方法是使用正则表达式检测此类对象。

关于
key=pickle.dumps((args,sorted(kwargs.items()),-1)

这似乎是一种比str()或repr()更稳健的方法。

这与EMS所说的类似,但最好的方法是:

key = cPickle.dumps((*args, **kwargs))
我一直在与装饰师进行大量的记忆研究和测试,这是迄今为止我发现的最好的方法。

这里:

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()函数:

+1非常好的观点!我将纠正这个问题,因为这不是主要的观点。顺便说一句,这会使第一个使用frozenset的方法无效吗?我不知道关于
frozenset
的契约的细节,但我相信它可以。Memorization通常使用arg作为dict的键,并带有结果,以及
哈希(排序({key':'arg')).items())
失败,但是
散列(frozenset({'key':'arg'}.items())
工作。+1非常好的一点,第二种方法是由数据的
\str\uuuuuuuu
\uuuuu repr\uuuuu
方法决定的。您对
frozenset(kwargs.items())的顺序是否有感觉
对于同一平台上的同一个
kwargs
是确定性的?而您不能使用
sorted()
在任意的dict上,kwargs会有str键。@juanchopanza:a
set
frozenset
没有排序行为,因此可能排除了第一个选项。但是可能是排序
kwargs.items()的
元组
。。。
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()