ленивые переменные питона?или отложенное дорогое вычисление - PullRequest
12 голосов
/ 22 августа 2011

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

Пример того, как это сейчас:

x = function_that_generates_huge_array_slowly(0)
y = function_that_generates_huge_array_slowly(1)

Пример того, что я хотел бы сделать:

x = lambda: function_that_generates_huge_array_slowly(0)
y = lambda: function_that_generates_huge_array_slowly(1)
z = x * 5 # this doesn't work because lambda is a function
      # is there something that would make this line behave like
      # z = x() * 5?
g = x * 6

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

EDIT: После некоторого дополнительного поиска, похоже, что можно делать то, что я хочу (приблизительно) с «ленивыми» атрибутами в классе (например, http://code.activestate.com/recipes/131495-lazy-attributes/).. Я не думаю, что есть какой-либо способ сделать что-то подобное без создания отдельный класс?

РЕДАКТИРОВАТЬ2: Я пытаюсь реализовать некоторые из решений, но я столкнулся с проблемой, потому что я не понимаю разницу между:

class sample(object):
    def __init__(self):
        class one(object):
            def __get__(self, obj, type=None):
                print "computing ..."
                obj.one = 1
                return 1
        self.one = one()

и

class sample(object):
    class one(object):
        def __get__(self, obj, type=None):
            print "computing ... "
            obj.one = 1
            return 1
    one = one()

Я думаю, что это то, что я искал, потому что дорогие переменные должны быть частью класса.

Ответы [ 5 ]

7 голосов
/ 22 августа 2011

Первая половина вашей проблемы (повторное использование значения) легко решается:

class LazyWrapper(object):
    def __init__(self, func):
        self.func = func
        self.value = None
    def __call__(self):
        if self.value is None:
            self.value = self.func()
        return self.value

lazy_wrapper = LazyWrapper(lambda: function_that_generates_huge_array_slowly(0))

, но вы все равно должны использовать ее как lazy_wrapper(), а не lasy_wrapper.

ЕслиВы собираетесь обращаться к некоторым переменным много раз, возможно, будет быстрее использовать:

class LazyWrapper(object):
    def __init__(self, func):
        self.func = func
    def __call__(self):
        try:
            return self.value
        except AttributeError:
            self.value = self.func()
            return self.value

, что сделает первый вызов медленнее, а последующие - быстрее.

Edit: Я вижу, вы нашли похожее решение, которое требует от вас использовать атрибуты в классе.В любом случае вам нужно переписать каждый ленивый доступ к переменной, поэтому просто выберите любой, какой вам нравится.

Редактировать 2: Вы также можете сделать:

class YourClass(object)
    def __init__(self, func):
        self.func = func
    @property
    def x(self):
        try:
            return self.value
        except AttributeError:
            self.value = self.func()
            return self.value

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

6 голосов
/ 22 августа 2011

Написание класса является более надежным, но оптимизирующим для простоты (который, я думаю, вы просите), я пришел к следующему решению:

cache = {}

def expensive_calc(factor):
    print 'calculating...'
    return [1, 2, 3] * factor

def lookup(name):
    return ( cache[name] if name in cache
        else cache.setdefault(name, expensive_calc(2)) )

print 'run one'
print lookup('x') * 2

print 'run two'
print lookup('x') * 2
5 голосов
/ 05 октября 2018

В Python 3.2 и выше реализован алгоритм LRU в модуле functools для обработки простых случаев кэширования / запоминания:

import functools

@functools.lru_cache(maxsize=128) #cache at most 128 items
def f(x):
    print("I'm being called with %r" % x)
    return x + 1

z = f(9) + f(9)**2
2 голосов
/ 06 января 2019

Мне кажется, что правильным решением для вашей проблемы является использование подкласса dict и его использование.

class LazyDict(dict):
    def __init__(self, lazy_variables):
        self.lazy_vars = lazy_variables
    def __getitem__(self, key):
        if key not in self and key in self.lazy_vars:
            self[key] = self.lazy_vars[key]()
        return super().__getitem__(key)

def generate_a():
    print("generate var a lazily..")
    return "<a_large_array>"

# You can add more variables as many as you want here
lazy_vars = {'a': generate_a}

lazy = LazyDict(lazy_vars)

# retrieve the variable you need from `lazy`
a = lazy['a']
print("Got a:", a)

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

your_code = "print('inside exec');print(a)"
exec(your_code, lazy)

Если бы вы сделали your_code = open(your_file).read(), вы могли бы на самом деле запустить свой код и достичь того, что вы хотите. Но я думаю, что более практичный подход был бы прежним.

2 голосов
/ 23 августа 2011

Вы не можете сделать простое имя, например x, чтобы действительно лениво оценивать.Имя - это просто запись в хеш-таблице (например, в той, которую возвращает locals() или globals()).Если вы не пропатчите методы доступа к этим системным таблицам, вы не сможете присоединить выполнение вашего кода к простому разрешению имен.

Но вы можете обернуть функции в обертки кэширования различными способами.Это ОО-способ:

class CachedSlowCalculation(object):
    cache = {} # our results

    def __init__(self, func):
        self.func = func

    def __call__(self, param):
        already_known = self.cache.get(param, None)
        if already_known:
            return already_known
        value = self.func(param)
        self.cache[param] = value
        return value

calc = CachedSlowCalculation(function_that_generates_huge_array_slowly)

z = calc(1) + calc(1)**2 # only calculates things once

Это бесклассовый способ:

def cached(func):
    func.__cache = {} # we can attach attrs to objects, functions are objects
    def wrapped(param):
        cache = func.__cache
        already_known = cache.get(param, None)
        if already_known:
            return already_known
        value = func(param)
        cache[param] = value
        return value
    return wrapped

@cached
def f(x):
    print "I'm being called with %r" % x
    return x + 1

z = f(9) + f(9)**2 # see f called only once

В реальном мире вы добавите логику, чтобы сохранить кеш разумного размера, возможно, используяалгоритм LRU.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...