Подтвердить что ты не робот

Ленивая оценка в Python

Что такое ленивая оценка в Python?

Один сайт сказал:

В Python 3.x функция range() возвращает специальный объект диапазона, который вычисляет элементы списка по запросу (отложенная или отложенная оценка):

>>> r = range(10)
>>> print(r)
range(0, 10)
>>> print(r[3])
3

Что подразумевается под этим?

4b9b3361

Ответ 1

Объект, возвращаемый range() (или xrange() в Python2.x), называется generator.

Вместо хранения всего диапазона [0,1,2,..,9] в памяти генератор сохраняет определение для (i=0; i<10; i+=1) и вычисляет следующее значение только тогда, когда это необходимо (AKA lazy-evaluation).

По существу, генератор позволяет вам возвращать список как структуру, но вот некоторые отличия:

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

Генератор может быть создан двумя способами:

(1) Очень похоже на понимание списка:

# this is a list, create all 5000000 x/2 values immediately, uses []
lis = [x/2 for x in range(5000000)]

# this is a generator, creates each x/2 value only when it is needed, uses ()
gen = (x/2 for x in range(5000000)) 

(2) Как функция, используя yield, чтобы вернуть следующее значение:

# this is also a generator, it will run until a yield occurs, and return that result.
# on the next call it picks up where it left off and continues until a yield occurs...
def divby2(n):
    num = 0
    while num < n:
        yield num/2
        num += 1

# same as (x/2 for x in range(5000000))
print divby2(5000000)

Примечание: Даже если range(5000000) является генератором в Python3.x, [x/2 for x in range(5000000)] по-прежнему является списком. range(...) выполняет задание и генерирует x по одному, но весь список значений x/2 будет вычисляться при создании этого списка.

Ответ 2

Вкратце, ленивая оценка означает, что объект оценивается, когда это необходимо, а не когда оно создается.

В Python 2 диапазон вернет список - это означает, что если вы дадите ему большое количество, он вычислит диапазон и вернется во время создания:

>>> i = range(100)
>>> type(i)
<type 'list'>

В Python 3, однако, вы получаете специальный объект диапазона:

>>> i = range(100)
>>> type(i)
<class 'range'>

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

Ответ 3

Репозиторий Github под названием Python Patterns и Википедия говорят нам, что такое ленивая оценка.

Задерживает вычисление expr до тех пор, пока его значение не потребуется, и избегает повторных evals.

range в python3 не является полной ленивой оценкой, потому что он не избегает повторного eval.

Более классический пример для ленивых вычислений - cached_property:

import functools

class cached_property(object):
    def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)

    def __get__(self, obj, type_):
        if obj is None:
            return self
        val = self.function(obj)
        obj.__dict__[self.function.__name__] = val
        return val

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

например:

class LogHandler:
    def __init__(self, file_path):
        self.file_path = file_path

    @cached_property
    def load_log_file(self):
        with open(self.file_path) as f:
            # the file is to big that I have to cost 2s to read all file
            return f.read()

log_handler = LogHandler('./sys.log')
# only the first time call will cost 2s.
print(log_handler.load_log_file)
# return value is cached to the log_handler obj.
print(log_handler.load_log_file)

Чтобы использовать правильное слово, объект генератора питона, такой как range, больше похож на шаблон call_by_need, а не на ленивую оценку

Ответ 4

Оценка ленивых переменных Python 3 с @functools.lru_cache

Для пользователей Google, желающих выполнять ленивую оценку переменных в Python, посетите https://docs.python.org/3.7/library/functools.html#functools.lru_cache

Пример из документов:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

См. Также: существует ли декоратор для простого кэширования возвращаемых значений функции?