Ленивый вычислитель Python

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

обратите внимание, что memoization может быть не идеально подходит.

Я:

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

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

EDIT: я предположил, что f должна быть функция, но это может быть не так.

7 ответов


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


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

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) (Найдено здесь: есть ли декоратор, чтобы просто кэшировать возвращаемые значения функции? Алекс Мартелли)

EDIT: вот еще один в виде свойств (используя __get__) http://code.activestate.com/recipes/363602/


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

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. В этом сила декораторов.


есть довольно много декораторов там для memoization:

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/

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

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

EDIT: поэтому я думаю, что вы действительно хотите ленивой оценки в конце концов. Вот библиотека, которая, вероятно, то, что вы ищете для:

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


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

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


здесьдовольно короткий ленивый декоратор, хотя ему не хватает использования @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

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

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

Я:

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

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

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

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

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

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