Как запомнить * * кварги?

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

def f(*args, **kwargs)

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

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

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

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

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

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

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

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

5 ответов


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

это "лучшее", что вы можете сделать, не делая предположений о ваших данных.

однако, кажется, можно захотеть выполнить memoization на словарях (немного необычно, хотя), вы могли бы в частном случае, если бы вы этого захотели. Например, вы можете рекурсивно применить frozenset(---.items()) при копировании словарей.


если у вас sorted, вы могли бы быть в плохой ситуации, когда у вас есть ключи unorderable. Например, "подмножество и равенство сравнения не обобщаются на функцию полного упорядочения. Например, любые два непересекающихся множества не равны и не являются подмножествами друг друга, поэтому все следующие возвращать false: АБ. Соответственно, наборы не реализуют 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() на произвольных диктах, у кваргов будут ключи str.- Это совершенно верно. Таким образом, это не проблема для ключей, хотя, возможно что-то, что нужно иметь в виду для значений, если вы (или маловероятный repr) как-то полагаетесь на сортировку.


о 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 функции. Как уже упоминалось, один, возможно, действительно hackish обходной путь заключается в обнаружении таких объектов с регулярным выражением.


словарь может быть в произвольном порядке, поэтому нет никакой гарантии, что оно будет работать. Использовать 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()

это работает до тех пор, пока вы не передаете unhashable тип (например, dict) в качестве аргумента. У меня нет решения для этого, кроме коллекций.реализация lru_cache() может иметь. См. функцию _make_key () здесь: http://code.activestate.com/recipes/578078/


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