python 3: настройка переменной один раз внутри функции, которая вызывается несколько раз [дубликат]

Другим вариантом, если производительность является проблемой, является использование расширения data.table для расширения reshape2 расплава & amp; dcast functions

( Ссылка: эффективная перестройка с использованием data.tables )

library(data.table)

setDT(dat1)
dcast(dat1, name ~ numbers, value.var = "value")

#          name          1          2         3         4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814

И, как и в data.table v1.9.6, мы можем использовать несколько столбцов

## add an extra column
dat1[, value2 := value * 2]

## cast multiple value columns
dcast(dat1, name ~ numbers, value.var = c("value", "value2"))

#          name    value_1    value_2   value_3   value_4   value2_1   value2_2 value2_3  value2_4
# 1:  firstName  0.1836433 -0.8356286 1.5952808 0.3295078  0.3672866 -1.6712572 3.190562 0.6590155
# 2: secondName -0.8204684  0.4874291 0.7383247 0.5757814 -1.6409368  0.9748581 1.476649 1.1515627

307
задан Michael Durrant 29 October 2011 в 11:53
поделиться

13 ответов

Memoization в основном сохраняет результаты прошлых операций, выполняемых с помощью рекурсивных алгоритмов, чтобы уменьшить необходимость пересечения дерева рекурсии, если тот же расчет требуется на более позднем этапе.

см. http : //scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

Пример Memoization Fibonacci в 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]
6
ответ дан Aaron Hall 20 August 2018 в 20:41
поделиться
  • 1
    Для большей производительности предварительно высекайте свой фикчач с помощью первых нескольких известных значений, тогда вы можете взять дополнительную логику для обработки их из «горячего пути» кода. – jkflying 21 May 2014 в 06:59

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

Вот пример:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]

Более полное описание можно найти в записи wikipedia в memoization .

16
ответ дан Bryan Oakley 20 August 2018 в 20:41
поделиться
  • 1
    Хм, теперь, если бы это был правильный Python, он бы качался, но, похоже, это не так ... хорошо, так что «кеш» это не дикт? Потому что, если это так, это должно быть if input not in self.cache и self.cache[input] (has_key устарело, поскольку ... в начале серии 2.x, если не 2.0. self.cache(index) никогда не был прав. IIRC) – Jürgen A. Erhard 1 January 2010 в 16:46

Решение, которое работает как с аргументами positional, так и с ключевыми словами независимо от порядка, в котором были переданы ключевые слова args (используя 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 для memoization в Python

2
ответ дан Community 20 August 2018 в 20:41
поделиться

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

5
ответ дан Conal 20 August 2018 в 20:41
поделиться

Давайте не будем забывать о встроенной функции hasattr, для тех, кто хочет заниматься ремеслом. Таким образом, вы можете хранить кеш-память внутри определения функции (в отличие от глобального).

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]
9
ответ дан David 20 August 2018 в 20:41
поделиться

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

167
ответ дан Flimm 20 August 2018 в 20:41
поделиться
  • 1
    Пробовал фид (1000), получил RecursionError: максимальная глубина рекурсии превышена в сравнении – Andyk 28 September 2017 в 12:04
  • 2
    @Andyk. Предел рекурсии по умолчанию Py3 равен 1000. При первом вызове fib он должен будет вернуться к базовому регистру до того, как произойдет мемонирование. Итак, ваше поведение как раз и ожидается. – Quelklef 19 August 2018 в 02:07
  • 3
    – Kristada673 22 October 2018 в 02:20
  • 4
  • 5
    – Pranav Vempati 25 October 2018 в 19:24

Я нашел это чрезвычайно полезным

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)
7
ответ дан mr.bjerre 20 August 2018 в 20:41
поделиться

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

Как правило, memoisation - это операция, которую вы можете применить к любой функции, которая что-то вычисляет (дорого) и возвращает значение. Из-за этого он часто реализуется как декоратор . Реализация прост, и это будет нечто подобное

memoised_function = memoise(actual_function)

или выражено в качестве декоратора

@memoise
def actual_function(arg1, arg2):
   #body
56
ответ дан Noufal Ibrahim 20 August 2018 в 20:41
поделиться

Мемонирование эффективно относится к запоминанию («воспоминания» → «меморандум» → для запоминания) результаты вызовов методов на основе входных данных метода, а затем возврат запоминаемого результата, а не повторение вычисления результата. Вы можете думать об этом как кэш для результатов метода. Более подробную информацию см. На стр. 387 для определения в . Введение в алгоритмы (3e), Cormen и др.

. Простой пример вычисления факториалов с использованием memoization в 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]

Вы можете усложниться и инкапсулировать процесс memoization в класс:

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)

Функция известный как « decorators », был добавлен в Python 2.4, который позволяет вам просто написать следующее, чтобы выполнить одно и то же:

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

Библиотека декораторов питона имеет аналогичный декоратор, называемый memoized , который немного более устойчив, чем класс Memoize, показанный здесь.

302
ответ дан Richard 20 August 2018 в 20:41
поделиться
  • 1
    Спасибо за это предложение. Класс Memoize - это элегантное решение, которое может быть легко применено к существующему коду без необходимости многого рефакторинга. – Captain Lepton 11 April 2013 в 13:41
  • 2
    Решение класса Memoize не работает, оно не будет работать так же, как factorial_memo, потому что factorial внутри def factorial все еще вызывает старое unmemoize factorial. – adamsmith 6 August 2013 в 08:35
  • 3
    Кстати, вы также можете написать if k not in factorial_memo:, который лучше, чем if not k in factorial_memo:. – ShreevatsaR 4 April 2014 в 07:34
  • 4
    Должно действительно делать это как декоратор. – Emlyn O'Regan 8 October 2014 в 05:23
  • 5
    @ durden2.0 Я знаю, что это старый комментарий, но args является кортежем. def some_function(*args) делает args кортежем. – Adam Smith 13 December 2016 в 19:18

Вот решение, которое будет работать со списком или аргументом типа 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)))
4
ответ дан RussellStewart 20 August 2018 в 20:41
поделиться
  • 1
    Хорошая попытка. Без нытья аргумент list [1, 2, 3] можно ошибочно считать тем же самым, что и другой аргумент set со значением {1, 2, 3}. Кроме того, наборы неупорядочены, как словари, поэтому они также должны быть sorted(). Также обратите внимание, что аргумент рекурсивной структуры данных вызовет бесконечный цикл. – martineau 20 January 2014 в 03:31
  • 2
    Да, комплекты должны обрабатываться специальным корпусом handle_item (x) и сортировкой. Я не должен был говорить, что эта реализация обрабатывает множества, потому что это не так, - но дело в том, что это может быть легко распространено для этого специальным дескриптором handle_item, и то же самое будет работать для любого класса или итерируемого объекта, пока вы готовы сами написать функцию хэш-функции. Сложная часть - работа с многомерными списками или словарями - уже рассмотрена здесь, поэтому я обнаружил, что эта функция memoize намного проще работать с базой, чем простая «я принимаю только аргументы хэширования», типы. – RussellStewart 21 January 2014 в 03:36
  • 3
    Проблема, о которой я упоминал, связан с тем, что list s и set s являются «упорядоченными». в одно и то же, и поэтому становятся неотличимыми друг от друга. Пример кода для добавления поддержки для sets, описанного в вашем последнем обновлении, не исключает, что я боюсь. Это можно легко увидеть, передав [1,2,3] и {1,2,3} отдельно в качестве аргумента в функцию «memoize» d test и видя, называется ли она дважды, как и должно быть, или нет. – martineau 21 January 2014 в 04:07
  • 4
    да, я читал эту проблему, но я не обращался к ней, потому что я думаю, что она намного меньше, чем вы упомянули. Когда вы в последний раз написали memoized функцию, где фиксированный аргумент мог быть либо списком, либо набором, а два привели к разным выходам? Если бы вы столкнулись с таким редким случаем, вы снова просто перепишите handle_item в preend, скажем, 0, если элемент - это набор, или 1, если это список. – RussellStewart 22 January 2014 в 02:14
  • 5
    На самом деле существует аналогичная проблема с list s и dict s, потому что для <[i3> возможно ] для того, чтобы list имел в точности то же самое, что и в результате вызова make_tuple(sorted(x.items())) для словаря. Простым решением для обоих случаев было бы включение значения type() в генерируемый набор. Я могу придумать еще более простой способ обработки set s, но он не обобщает. – martineau 22 January 2014 в 04:49

Просто хотел добавить к уже предоставленным ответам, библиотека декораторов Python имеет несколько простых, но полезных реализаций, которые также могут memoize «unhashable types», в отличие от functools.lru_cache.

2
ответ дан Sid 20 August 2018 в 20:41
поделиться
  • 1
    Этот декоратор не memoize & quot; unsaashable types & quot; ! Он просто возвращается к вызову функции без memoization, и против iQ явственно лучше, чем неявная догма. – ostrokach 1 June 2016 в 19:47
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]
2
ответ дан Vikrant Singh 20 August 2018 в 20:41
поделиться
  • 1
    вы можете использовать просто if n not in cache. используя cache.keys, создаст ненужный список в python 2 – n611x007 29 January 2013 в 11:53

Ну, я должен сначала ответить на первую часть: что такое memoization?

Это всего лишь метод для торговли памятью за время. Подумайте о таблице умножения .

Использование изменяемого объекта в качестве значения по умолчанию в Python обычно считается плохим. Но если использовать его с умом, на самом деле может быть полезно реализовать memoization.

Вот пример, адаптированный из http://docs.python.org/2/faq/design.html # why-are-default-values-shared-between-objects

Используя mutable 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]
4
ответ дан yegle 20 August 2018 в 20:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: