Python ленивый оценщик - PullRequest
       34

Python ленивый оценщик

7 голосов
/ 14 марта 2011

Есть ли Pythonic способ инкапсулировать ленивый вызов функции, при котором при первом использовании функции f() он вызывает ранее связанную функцию g(Z), а при последующих вызовах f() возвращает кэшированное значение?

Обратите внимание, что памятка может не подходить идеально.

У меня есть:

f = g(Z)
if x:
     return 5
elif y:
     return f
elif z:
     return h(f)

Код работает, но я хочу реструктурировать его так, чтобы g(Z) был толькоВызывается, если используется значение.Я не хочу менять определение g(...), а Z немного велика для кеширования.

РЕДАКТИРОВАТЬ: Я предполагал, что f должна быть функцией, но это не можетбудь случай.

Ответы [ 7 ]

6 голосов
/ 14 марта 2011

Я немного запутался, ищите ли вы кеширование или ленивую оценку.Для последнего, проверьте модуль lazy.py Альберто Бертогли .

3 голосов
/ 14 марта 2011

Попробуйте использовать этот декоратор:

class Memoize:
    def __init__ (self, f):
        self.f = f
        self.mem = {}
    def __call__ (self, *args, **kwargs):
        if (args, str(kwargs)) in self.mem:
            return self.mem[args, str(kwargs)]
        else:
            tmp = self.f(*args, **kwargs)
            self.mem[args, str(kwargs)] = tmp
            return tmp

(извлечено из неработающей ссылки: http://snippets.dzone.com/posts/show/4840 / https://web.archive.org/web/20081026130601/http://snippets.dzone.com/posts/show/4840) (Найдено здесь: Существует ли декоратор для простого кэшированияфункция возвращает значения? (Алекс Мартелли)

РЕДАКТИРОВАТЬ: Вот еще один в форме свойств (используя __get__) http://code.activestate.com/recipes/363602/

1 голос
/ 18 августа 2014

Здесь - довольно короткий ленивый декоратор, хотя в нем не используется @functools.wraps (и фактически возвращает экземпляр Lazy плюс некоторые другие потенциальные ловушки):

class Lazy(object):
    def __init__(self, calculate_function):
        self._calculate = calculate_function

    def __get__(self, obj, _=None):
        if obj is None:
            return self
        value = self._calculate(obj)
        setattr(obj, self._calculate.func_name, value)
        return value


# Sample use:

class SomeClass(object):

    @Lazy
    def someprop(self):
        print 'Actually calculating value'
        return 13


o = SomeClass()
o.someprop
o.someprop
1 голос
/ 02 апреля 2012

Просто для полноты вот ссылка на мой рецепт декоратора для ленивых оценщиков:

https://bitbucket.org/jsbueno/metapython/src/f48d6bd388fd/lazy_decorator.py

1 голос
/ 14 марта 2011

Вы можете использовать декоратор кеша, давайте рассмотрим пример

from functools import wraps

class FuncCache(object):
    def __init__(self):
        self.cache = {}

    def __call__(self, func):
        @wraps(func)
        def callee(*args, **kwargs):
            key = (args, str(kwargs))
            # see is there already result in cache
            if key in self.cache:
                result = self.cache.get(key)
            else:
                result = func(*args, **kwargs)
                self.cache[key] = result
            return result
        return callee

С декоратором кеша здесь вы можете написать

my_cache = FuncCache()

@my_cache
def foo(n):
    """Expensive calculation

    """
    sum = 0
    for i in xrange(n):
        sum += i
    print 'called foo with result', sum
    return sum

print foo(10000)
print foo(10000)
print foo(1234)

Как видно из вывода

called foo with result 49995000
49995000
49995000

Foo будет вызываться только один раз.Вам не нужно менять строку вашей функции foo.Это сила декораторов.

1 голос
/ 14 марта 2011

Существует довольно много декораторов для памятки:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize http://code.activestate.com/recipes/498110-memoize-decorator-with-o1-length-limited-lru-cache/ http://code.activestate.com/recipes/496879-memoize-decorator-function-with-cache-size-limit/

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

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

РЕДАКТИРОВАТЬ: Итак, я думаю, что вы действительно хотите ленивые оценки в конце концов. Вот библиотека, которая, вероятно, то, что вы ищете:

http://pypi.python.org/pypi/lazypy/0.5

0 голосов
/ 14 марта 2011

Даже после твоего редактирования и серии комментариев с детства я до сих пор не совсем понял.В первом предложении вы говорите, что первый вызов функции f () должен вызывать g (), но впоследствии возвращает кешированные значения.Но затем в своих комментариях вы говорите: «g () не не вызывается, несмотря ни на что» (выделено мое).Я не уверен, что вы отрицаете: Вы говорите, что g () должен никогда не вызываться (не имеет особого смысла; почему g () существует?);или что g () может быть вызвано , но не может (ну, это все равно противоречит тому, что g () вызывается при первом вызове f ()).Затем вы даете фрагмент, который вообще не включает g () и действительно не относится ни к первому предложению вашего вопроса, ни к цепочке комментариев с умением.

В случае, если вы приступите к редактированиюснова, вот фрагмент, на который я отвечаю:

У меня есть:

a = f(Z)
if x:
     return 5
elif y:
     return a
elif z:
     return h(a)

Код работает, но я хочу реструктурировать его так, чтобы f (Z) быловызывается только если используется значение.Я не хочу менять определение f (...), и Z немного велика для кеширования.

Если это действительно ваш вопрос, то ответ просто

if x:
    return 5
elif y:
    return f(Z)
elif z:
    return h(f(Z))

Именно так можно добиться, чтобы «f (Z) вызывалось только в том случае, если используется значение».

Я не совсем понимаю, «Z немного велика для кеширования».Если вы имеете в виду, что в ходе выполнения программы будет слишком много разных значений Z, что запоминание бесполезно, то, возможно, вам придется прибегнуть к предварительному расчету всех значений f (Z) и просто посмотреть их во время выполнения.Если вы не можете сделать это (потому что вы не можете знать значения Z, с которыми столкнется ваша программа), тогда вы вернетесь к запоминанию.Если это все еще слишком медленно, то ваш единственный реальный вариант - использовать что-то быстрее, чем Python (попробуйте модуль Psyco, Cython, ShedSkin или C-код, написанный вручную).

...