Рассмотреть возможность

def f(x,*args):
    intermediate = computationally_expensive_fct(x)
    return do_stuff(intermediate,*args)

Проблема: для одного и того же x эту функцию можно вызывать тысячи раз с разными аргументами (кроме x ) и каждый раз, когда функция будет назван промежуточный (факторизация Холецкого, стоимость O (n ^ 3)). В принципе, однако, достаточно, если для каждого x промежуточный вычисляется только один раз для каждого x , а затем этот результат будет использоваться снова и снова е с разными аргументами.

Моя идея . Чтобы исправить это, я попытался создать глобальный словарь, в котором функция проверяет, был ли дорогой материал для своего параметра x уже сделан и сохранен в словаре или должен ли он вычислять это:

if all_intermediates not in globals():
    global all_intermediates = {}

if all_intermediates.has_key(x):
    pass
else:
    global all_intermediates[x] = computationally_expensive_fct(x)

Оказывается, я не могу этого сделать, потому что globals () сам по себе является dict, а в python нельзя хешировать dict. Я начинающий программист, и был бы рад, если бы кто-нибудь указал мне на питонный способ сделать то, чего я хочу достичь.

2
sbm 19 Дек 2015 в 17:13

3 ответа

Лучший ответ

Решение

Немного легче, чем писать декоратор и без доступа к глобальным переменным:

def f(x, *args):
    if not hasattr(f, 'all_intermediates'):
        f.all_intermediates = {}
    if x not in f.all_intermediates:
        f.all_intermediates[x] = computationally_expensive_fct(x)
    intermediate = f.all_intermediates[x]
    return do_stuff(intermediate,*args)

Варьирование

Вариант, который избегает if not hasattr, но должен установить all_intermediates как атрибут f после его определения:

def f(x, *args):
    if x not in f.all_intermediates:
        f.all_intermediates[x] = computationally_expensive_fct(x)
    intermediate = f.all_intermediates[x]
    return do_stuff(intermediate,*args)
f.all_intermediates = {}

Это кэширует all_intermediates как атрибут самой функции.

Объяснение

Функции являются объектами и могут иметь атрибуты. Следовательно, вы можете сохранить словарь all_intermediates как атрибут функции f. Это делает функцию самодостаточной, то есть вы можете переместить ее в другой модуль, не беспокоясь о глобальных значениях модуля. Используя вариант, показанный выше, вам нужно переместить f.all_intermediates = {} вместе с функцией.

Помещать вещи в globals() нехорошо. Я рекомендую не делать этого.

2
Mike Müller 19 Дек 2015 в 14:42

Это часто реализуется с помощью декоратора @memoized в дорогой функции.

Он описан по адресу https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize. и достаточно кратко, чтобы дублировать здесь в случае гниения ссылки:

import collections
import functools

class memoized(object):
   '''Decorator. Caches a function's return value each time it is called.
   If called later with the same arguments, the cached value is returned
   (not reevaluated).
   '''
   def __init__(self, func):
      self.func = func
      self.cache = {}
   def __call__(self, *args):
      if not isinstance(args, collections.Hashable):
         # uncacheable. a list, for instance.
         # better to not cache than blow up.
         return self.func(*args)
      if args in self.cache:
         return self.cache[args]
      else:
         value = self.func(*args)
         self.cache[args] = value
         return value
   def __repr__(self):
      '''Return the function's docstring.'''
      return self.func.__doc__
   def __get__(self, obj, objtype):
      '''Support instance methods.'''
      return functools.partial(self.__call__, obj)

Как только дорогая функция запоминается, ее использование невидимо:

@memoized
def expensive_function(n):
   # expensive stuff
   return something

p = expensive_function(n)
q = expensive_function(n)
assert p is q

Обратите внимание, что если результат expensive_function не может быть хешируемым (списки являются типичным примером), производительность не увеличится, он все равно будет работать, но будет действовать так, как если бы он не запоминался.

1
msw 19 Дек 2015 в 15:10

Я не понимаю, почему вы пытаетесь использовать globals(). Вместо использования globals() вы можете просто хранить вычисленные значения в своем собственном словаре уровня модуля и иметь функцию-оболочку, которая проверяет, вычислен ли intermediate или нет. Что-то вроде этого:

computed_intermediate = {}

def get_intermediate(x):
    if x not in computed_intermediate:
        computed_intermediate[x] = computationally_expensive_fct(x)

    return computed_intermediate[x]

def f(x,*args):
    intermediate = get_intermediate(x)
    return do_stuff(intermediate,*args)

Таким образом, computationally_expensive_fct(x) будет рассчитываться только один раз для каждого x, а именно при первом обращении к нему.

1
taskinoor 19 Дек 2015 в 14:25