Я только начал Python, и я понятия не имею, что такое памятка и как ее использовать. Кроме того, могу ли я иметь упрощенный пример?
Я только начал Python, и я понятия не имею, что такое памятка и как ее использовать. Кроме того, могу ли я иметь упрощенный пример?
Ответы:
Под «запоминанием» фактически понимается запоминание («памятка» → «меморандум» → запоминание) результатов вызовов метода на основе входных данных метода и затем возвращение запомненного результата, а не его повторный расчет. Вы можете думать об этом как о кеше для результатов метода. Для получения дополнительной информации см. Стр. 387 для определения в разделе Введение в алгоритмы (3e), Cormen et al.
Простой пример вычисления факториалов с использованием мемоизации в Python будет выглядеть примерно так:
factorial_memo = {}
def factorial(k):
if k < 2: return 1
if k not in factorial_memo:
factorial_memo[k] = k * factorial(k-1)
return factorial_memo[k]
Вы можете усложнить задачу и включить процесс запоминания в класс:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
#Warning: You may wish to do a deepcopy here if returning objects
return self.memo[args]
Затем:
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
factorial = Memoize(factorial)
В Python 2.4 была добавлена функция, известная как « декораторы », которая позволяет вам просто написать следующее, чтобы выполнить то же самое:
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
В библиотеке Python Decorator есть похожий декоратор, memoized
который называется несколько более надежным, чем Memoize
класс, показанный здесь.
factorial_memo
, потому что factorial
внутренняя часть def factorial
все еще вызывает старую команду unmemoize factorial
.
if k not in factorial_memo:
, что читает лучше, чем if not k in factorial_memo:
.
args
это кортеж. def some_function(*args)
делает аргументы кортежем
Новое в Python 3.2 есть functools.lru_cache
. По умолчанию, он кэширует только 128 недавно использованные звонков, но вы можете установить , maxsize
чтобы None
указать , что кэш никогда не должен истекать:
import functools
@functools.lru_cache(maxsize=None)
def fib(num):
if num < 2:
return num
else:
return fib(num-1) + fib(num-2)
Эта функция сама по себе очень медленная, попробуйте, fib(36)
и вам придется подождать около десяти секунд.
Добавление lru_cache
аннотации гарантирует, что если функция была вызвана недавно для определенного значения, она не будет повторно вычислять это значение, а будет использовать кэшированный предыдущий результат. В этом случае это приводит к огромному улучшению скорости, в то время как код не загроможден деталями кэширования.
fib
вызове он должен вернуться к базовому сценарию, прежде чем произойдет запоминание. Итак, ваше поведение примерно ожидаемо.
Другие ответы охватывают то, что это довольно хорошо. Я не повторяю это. Просто некоторые моменты, которые могут быть полезны для вас.
Обычно запоминание - это операция, которую вы можете применить к любой функции, которая вычисляет что-то (дорогое) и возвращает значение. Из-за этого это часто реализуется как декоратор . Реализация проста, и это было бы что-то вроде этого
memoised_function = memoise(actual_function)
или выраженный как декоратор
@memoise
def actual_function(arg1, arg2):
#body
Мемоизация хранит результаты дорогостоящих вычислений и возвращает кэшированный результат, а не непрерывно пересчитывает его.
Вот пример:
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
Более полное описание можно найти в записи в Википедии о запоминании .
if input not in self.cache
и self.cache[input]
( has_key
устарело, поскольку ... в начале серии 2.x, если не 2.0, self.cache(index)
никогда не было верным. IIRC)
Давайте не будем забывать встроенную hasattr
функцию, для тех, кто хочет ручной работы. Таким образом, вы можете хранить кеш mem внутри определения функции (в отличие от глобального).
def fact(n):
if not hasattr(fact, 'mem'):
fact.mem = {1: 1}
if not n in fact.mem:
fact.mem[n] = n * fact(n - 1)
return fact.mem[n]
Я нашел это чрезвычайно полезным
def memoize(function):
from functools import wraps
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
functools.wraps
.
memo
чтобы освободить память?
Memoization - это, в основном, сохранение результатов прошлых операций, выполненных с помощью рекурсивных алгоритмов, чтобы уменьшить необходимость обхода дерева рекурсии, если такой же расчет требуется на более поздней стадии.
см. http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/
Пример запоминания Фибоначчи в Python:
fibcache = {}
def fib(num):
if num in fibcache:
return fibcache[num]
else:
fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
return fibcache[num]
Мемоизация - это преобразование функций в структуры данных. Обычно требуется, чтобы преобразование происходило постепенно и лениво (по требованию данного элемента домена - или «ключа»). В ленивых функциональных языках это ленивое преобразование может происходить автоматически, и, таким образом, запоминание может быть реализовано без (явных) побочных эффектов.
Ну, я должен ответить на первую часть в первую очередь: что такое запоминание?
Это просто способ обменять память на время. Подумайте о таблице умножения .
Использование изменяемого объекта в качестве значения по умолчанию в Python обычно считается плохим. Но если использовать его с умом, это может быть полезно для реализации memoization
.
Вот пример, адаптированный из http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects
Используя изменяемый dict
в определении функции, промежуточные вычисленные результаты могут быть кэшированы (например, при вычислении factorial(10)
после вычисления factorial(9)
мы можем повторно использовать все промежуточные результаты)
def factorial(n, _cache={1:1}):
try:
return _cache[n]
except IndexError:
_cache[n] = factorial(n-1)*n
return _cache[n]
Вот решение, которое будет работать с аргументами типа list или dict без нытья:
def memoize(fn):
"""returns a memoized version of any function that can be called
with the same list of arguments.
Usage: foo = memoize(foo)"""
def handle_item(x):
if isinstance(x, dict):
return make_tuple(sorted(x.items()))
elif hasattr(x, '__iter__'):
return make_tuple(x)
else:
return x
def make_tuple(L):
return tuple(handle_item(x) for x in L)
def foo(*args, **kwargs):
items_cache = make_tuple(sorted(kwargs.items()))
args_cache = make_tuple(args)
if (args_cache, items_cache) not in foo.past_calls:
foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
return foo.past_calls[(args_cache, items_cache)]
foo.past_calls = {}
foo.__name__ = 'memoized_' + fn.__name__
return foo
Обратите внимание, что этот подход можно естественным образом распространить на любой объект, реализовав собственную хеш-функцию в качестве особого случая в handle_item. Например, чтобы этот подход работал для функции, которая принимает набор в качестве входного аргумента, вы можете добавить к handle_item:
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
аргумент [1, 2, 3]
может ошибочно считаться другим set
аргументом со значением {1, 2, 3}
. Кроме того, наборы неупорядочены, как словари, поэтому они также должны быть sorted()
. Также обратите внимание, что рекурсивный аргумент структуры данных может вызвать бесконечный цикл.
list
s и set
s «дублируются» в одно и то же и поэтому становятся неотличимыми друг от друга. sets
Я не боюсь, что пример кода для добавления поддержки, описанный в вашем последнем обновлении. Это легко увидеть, отдельно передав [1,2,3]
и {1,2,3}
в качестве аргумента d-функции «memoize» и посмотрев, вызывается ли она дважды, как и должно быть, или нет.
list
с и dict
потому , что это возможно для list
иметь точно такую же вещь в нем , что в результате вызова make_tuple(sorted(x.items()))
для словаря. Простым решением для обоих случаев было бы включение type()
значения в сгенерированный кортеж. Я могу придумать еще более простой способ обработки set
s, но он не обобщает.
Решение, которое работает как с позиционными аргументами, так и с ключевыми словами независимо от порядка, в котором были переданы аргументы ключевого слова (с использованием inspect.getargspec ):
import inspect
import functools
def memoize(fn):
cache = fn.cache = {}
@functools.wraps(fn)
def memoizer(*args, **kwargs):
kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
if key not in cache:
cache[key] = fn(**kwargs)
return cache[key]
return memoizer
Аналогичный вопрос: определение эквивалентных вызовов функции varargs для запоминания в Python
cache = {}
def fib(n):
if n <= 1:
return n
else:
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
if n not in cache
вместо этого. использование cache.keys
создаст ненужный список в python 2
Хотелось бы добавить к уже предоставленным ответам, библиотека декоратора Python имеет несколько простых, но полезных реализаций, которые, в отличие от этого, могут также запоминать «не подлежащие обработке типы» functools.lru_cache
.