Подтвердить что ты не робот

Как memoize ** kwargs?

Я не видел установленного способа memoize функции, которая принимает аргументы ключевого слова, то есть что-то типа

def f(*args, **kwargs)

поскольку, как правило, memoizer имеет dict для кэширования результатов для заданного набора входных параметров, а kwargs является dict и, следовательно, недоступен. Я пробовал, после обсуждения здесь, используя

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

в качестве ключа к кешу dict, но это работает только в том случае, если значения в kwargs являются хешируемыми. Кроме того, как указано в ответах ниже, frozenset не является упорядоченной структурой данных. Поэтому это решение может быть более безопасным:

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

Но он все еще не может справиться с не-хэшируемыми элементами. Другой подход, который я видел, - использовать string представление kwargs в кеше:

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

Единственный недостаток, который я вижу с этим, - накладные расходы на хэширование потенциально очень длинной строки. Насколько я вижу, результаты должны быть правильными. Может ли кто-нибудь выявить какие-либо проблемы с последним подходом? Один из приведенных ниже ответов указывает, что это предполагает определенное поведение функций __str__ или __repr__ для значений аргументов ключевого слова. Это похоже на шоу-стоппер.

Есть ли еще один, более простой способ достижения memoization, который может справиться с **kwargs и значениями un-hashable?

4b9b3361

Ответ 1

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

edit: Игнасио говорит: "Пока вы не можете использовать sorted() для произвольных dicts, kwargs будет иметь str-ключи". Это совершенно правильно. Таким образом, это не проблема для ключей, хотя, возможно, что-то нужно помнить о значениях, если вы (или маловероятные репрезентации) полагаетесь на сортировку как-то.


Относительно использования str:

В этом случае большинство данные будут работать хорошо, но для противника может возникнуть конфликт (например, в контексте уязвимости безопасности). Вам нелегко думать, потому что большинство стандартных repr используют множество хороших группировок и побегов. На самом деле я не смог найти такое столкновение. Но это возможно с неряшливыми сторонними или неполными реализациями repr.


Также обратите внимание на следующее: если вы храните ключи типа ((<map object at 0x1377d50>,), frozenset(...)) и ((<list_iterator object at 0x1377dd0>,<list_iterator object at 0x1377dd0>), frozenset(...)), ваш кеш будет неограниченно расти, просто вызывая одни и те же элементы. (Возможно, вы можете обойти эту проблему с помощью регулярного выражения...) И попытка использовать генераторы испортит семантику функции, которую вы используете. Это может быть желательным поведением, но если вы хотите memoize на is -стилевое равенство, а не == -стилевое равенство.

Кроме того, что-то вроде str({1:object()}) в интерпретаторе будет возвращать объект в том же месте в памяти каждый раз! Я думаю, что это сборщик мусора на работе. Это было бы катастрофой, потому что, если вам посчастливилось хешировать <some object at 0x???????>, и позже вы создадите объект того же типа в том же месте памяти (из-за сбора мусора), вы получите неверные результаты из memoized функции. Как уже упоминалось, одним из возможных способов взлома является обнаружение таких объектов с помощью регулярного выражения.

Ответ 2

dicts может быть в произвольном порядке, поэтому нет гарантии, что последний будет работать. Используйте sorted(kwargs.items()), чтобы сначала отсортировать его по ключевым словам.

Ответ 3

Это похоже на то, что сказал EMS, но лучший способ:

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

Я проводил много исследований и тестов для запоминания с декораторами, и это лучший метод, который я нашел до сих пор.

Ответ 4

Как насчет key = pickle.dumps( (args, sorted(kwargs.items()), -1 )? Это, по-видимому, более надежный подход, чем str() или repr().

Ответ 5

Здесь:

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). У меня нет для этого решения, но у реализации collection.lru_cache() может быть. См. Здесь функцию _make_key(): http://code.activestate.com/recipes/578078/