Очевидно, что быстрый поиск дает миллион реализаций и разновидностей декоратора памятки в Python. Тем не менее, меня интересует аромат, который я не смог найти. Мне бы хотелось, чтобы он был таким, чтобы кэш хранимых значений мог иметь фиксированную емкость. Когда добавляются новые элементы, если достигается емкость, то самое старое значение удаляется и заменяется самым новым значением.
Меня беспокоит то, что, если я буду использовать памятку для хранения большого количества элементов, то программа будет аварийно завершать работу из-за нехватки памяти. (Я не знаю, насколько хорошо эта проблема может быть реализована на практике.) Если бы кэш был фиксированного размера, ошибка памяти не была бы проблемой. И многие проблемы, над которыми я работаю, меняются по мере выполнения программы, так что начальные кэшированные значения будут сильно отличаться от более поздних кэшированных значений (и с гораздо меньшей вероятностью будут повторяться позже). Вот почему я бы хотел, чтобы самые старые вещи были заменены новыми.
Я нашел класс OrderedDict
и пример, показывающий, как создать его подкласс для указания максимального размера. Я хотел бы использовать это как мой кеш, а не обычный dict
. Проблема в том, что мне нужно, чтобы декоратор memoize принял параметр с именем maxlen
, который по умолчанию равен None
. Если это None
, то кеш безграничен и работает как обычно. Любое другое значение используется в качестве размера для кэша.
Я хочу, чтобы это работало так:
@memoize
def some_function(spam, eggs):
# This would use the boundless cache.
pass
и
@memoize(200) # or @memoize(maxlen=200)
def some_function(spam, eggs):
# This would use the bounded cache of size 200.
pass
Ниже приведен код, который у меня есть, но я не вижу, как передать параметр в декоратор, заставляя его работать как «голым», так и с параметром.
import collections
import functools
class BoundedOrderedDict(collections.OrderedDict):
def __init__(self, *args, **kwds):
self.maxlen = kwds.pop("maxlen", None)
collections.OrderedDict.__init__(self, *args, **kwds)
self._checklen()
def __setitem__(self, key, value):
collections.OrderedDict.__setitem__(self, key, value)
self._checklen()
def _checklen(self):
if self.maxlen is not None:
while len(self) > self.maxlen:
self.popitem(last=False)
def memoize(function):
cache = BoundedOrderedDict() # I want this to take maxlen as an argument
@functools.wraps(function)
def memo_target(*args):
lookup_value = args
if lookup_value not in cache:
cache[lookup_value] = function(*args)
return cache[lookup_value]
return memo_target
@memoize
def fib(n):
if n < 2: return 1
return fib(n-1) + fib(n-2)
if __name__ == '__main__':
x = fib(50)
print(x)
Редактировать : Используя предложение Бена, я создал следующий декоратор, который, я считаю, работает так, как я себе представлял. Для меня важно иметь возможность использовать эти украшенные функции с multiprocessing
, и это было проблемой в прошлом. Но быстрая проверка этого кода, казалось, работала правильно, даже если обрабатывать задания в пуле потоков.
def memoize(func=None, maxlen=None):
if func:
cache = BoundedOrderedDict(maxlen=maxlen)
@functools.wraps(func)
def memo_target(*args):
lookup_value = args
if lookup_value not in cache:
cache[lookup_value] = func(*args)
return cache[lookup_value]
return memo_target
else:
def memoize_factory(func):
return memoize(func, maxlen=maxlen)
return memoize_factory