Как запомнить * * кварги?
я не видел установленного способа запоминания функции, которая принимает аргументы ключевого слова, т. е. что-то типа
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
:
это так большинство данные будут работать хорошо, но для противника (например, в контексте безопасности-уязвимости) возможно создать столкновение. Это нелегко, потому что большинство по умолчанию repr
s используйте много хорошей группировки и побега. На самом деле я не смог найти такого столкновения. Но это возможно с неаккуратный сторонний или неполный 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().