簡體   English   中英

如何回憶** kwargs?

[英]How to memoize **kwargs?

我還沒有看到一種既定的方法來記憶一個帶有關鍵字參數的函數,即類型的東西

def f(*args, **kwargs)

因為通常一個memoizer有一個dict來緩存給定輸入參數集的結果,而kwargs是一個dict ,因此是不可用的。 在這里討論后嘗試過使用

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

作為緩存dict關鍵,但這僅適用於kwargs中的值是可清除的。 此外,正如下面的答案中所指出的, frozenset不是有序數據結構。 因此,此解決方案可能更安全:

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

但它仍然無法應對不可清除的元素。 我看到的另一種方法是在緩存鍵中使用kwargsstring表示:

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

我看到的唯一缺點是散列可能非常長的字符串的開銷。 據我所知,結果應該是正確的。 有人能發現后一種方法有什么問題嗎? 下面的答案之一指出,這假定__str____repr__函數的某些行為用於關鍵字參數的值。 這似乎是一個顯示阻止。

是否有另一種更成熟的實現記憶的方法可以應對**kwargs和不可消除的參數值?

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 style等式而不是== -style相等,這可能是期望的行為。

在解釋器中執行類似str({1:object()}) ,每次都會在內存中的同一位置返回一個對象! 我認為這是工作中的垃圾收集器。 這將是災難性的,因為如果你碰巧哈希<some object at 0x???????>並且你碰巧在稍后的同一個內存位置創建了一個相同類型的對象(由於垃圾收集),你會從memoized函數得到不正確的結果。 如前所述,一個可能非常糟糕的解決方法是使用正則表達式檢測此類對象。

dicts可以是任意順序,所以不能保證后者可以工作。 使用sorted(kwargs.items())首先按鍵排序。

它與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()函數: http ://code.activestate.com/recipes/578078/

那么key = pickle.dumps( (args, sorted(kwargs.items()), -1 ) ?這似乎比str()或repr()更強大。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM