Ленивая оценка в Python
Что такое ленивая оценка в Python?
Один сайт сказал:
В Python 3.x функция range()
возвращает специальный объект диапазона, который вычисляет элементы списка по запросу (отложенная или отложенная оценка):
>>> r = range(10)
>>> print(r)
range(0, 10)
>>> print(r[3])
3
Что подразумевается под этим?
Ответы
Ответ 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)
См. Также: существует ли декоратор для простого кэширования возвращаемых значений функции?