Возможно ли ленивое вычисление списка в Python?
Например
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]
Если бы список был настроен на ленивое вычисление, то последняя строка была бы [2]
Возможно ли ленивое вычисление списка в Python?
Например
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]
Если бы список был настроен на ленивое вычисление, то последняя строка была бы [2]
Концепция «ленивого» вычисления обычно присутствует в функциональных языках - но в них вы не можете переназначить два разных значения одному и тому же идентификатору, поэтому даже ваш пример не может быть воспроизведен.
Дело вовсе не в лени - дело в том, что использование идентификатора гарантированно идентично получению ссылки на то же значение, на которое ссылается идентификатор, и повторному назначению идентификатора, < strong> голое имя с другим значением гарантированно заставит идентификатор ссылаться на другое значение, отличное от них. Ссылка на первое значение (объект) не теряется.
Рассмотрим аналогичный пример, в котором повторное присвоение пустому имени не используется, а скорее используется любой другой вид мутации (для изменяемого объекта, конечно - числа и строки неизменны), включая присвоение чему-то еще чем простое имя:
>>> a = [1]
>>> list = [a]
>>> print list
[[1]]
>>> a[:] = [2]
>>> print list
[[2]]
Поскольку нет a - ...
, который переназначает простое имя a
, а скорее a[:] = ...
, который переназначает содержимое a
, тривиально легко сделать Python настолько "ленивым", насколько вы хотите (и действительно, потребуются некоторые усилия, чтобы сделайте это «нетерпеливым»! -) ... если лень против рвения имеет какое-либо отношение к любому из этих случаев (а это не так ;-).
Просто имейте в виду совершенно простую семантику «присвоения простому имени» (по сравнению с назначением чему-либо еще, которое можно по-разному настраивать и контролировать, используя ваши собственные типы соответствующим образом), и можно надеяться, что оптическая иллюзия «ленивый против нетерпеливого» исчезнуть ;-)
Я наткнулся на этот пост, когда искал настоящую реализацию ленивого списка, но это звучало как забавная вещь, чтобы попробовать и поработать.
Следующая реализация делает в основном то, о чем изначально просили:
from collections import Sequence
class LazyClosureSequence(Sequence):
def __init__(self, get_items):
self._get_items = get_items
def __getitem__(self, i):
return self._get_items()[i]
def __len__(self):
return len(self._get_items())
def __repr__(self):
return repr(self._get_items())
Вы используете это так:
>>> a = 1
>>> l = LazyClosureSequence(lambda: [a])
>>> print l
[1]
>>> a = 2
>>> print l
[2]
Это явно ужасно.
Python вообще не очень ленив.
Вы можете использовать генераторы для имитации ленивых структур данных (например, бесконечных списков и т. Д.), Но что касается таких вещей, как использование обычного синтаксиса списков и т. Д., Вам не будет лени.
Это отложенный список только для чтения, для которого требуется только предварительно определенная length и функция cache-update:
import copy
import operations
from collections.abc import Sequence
from functools import partialmethod
from typing import Dict, Union
def _cmp_list(a: list, b: list, op, if_eq: bool, if_long_a: bool) -> bool:
"""utility to implement gt|ge|lt|le class operators"""
if a is b:
return if_eq
for ia, ib in zip(a, b):
if ia == ib:
continue
return op(ia, ib)
la, lb = len(a), len(b)
if la == lb:
return if_eq
if la > lb:
return if_long_a
return not if_long_a
class LazyListView(Sequence):
def __init__(self, length):
self._range = range(length)
self._cache: Dict[int, Value] = {}
def __len__(self) -> int:
return len(self._range)
def __getitem__(self, ix: Union[int, slice]) -> Value:
length = len(self)
if isinstance(ix, slice):
clone = copy.copy(self)
clone._range = self._range[slice(*ix.indices(length))] # slicing
return clone
else:
if ix < 0:
ix += len(self) # negative indices count from the end
if not (0 <= ix < length):
raise IndexError(f"list index {ix} out of range [0, {length})")
if ix not in self._cache:
... # update cache
return self._cache[ix]
def __iter__(self) -> dict:
for i, _row_ix in enumerate(self._range):
yield self[i]
__eq__ = _eq_list
__gt__ = partialmethod(_cmp_list, op=operator.gt, if_eq=False, if_long_a=True)
__ge__ = partialmethod(_cmp_list, op=operator.ge, if_eq=True, if_long_a=True)
__le__ = partialmethod(_cmp_list, op=operator.le, if_eq=True, if_long_a=False)
__lt__ = partialmethod(_cmp_list, op=operator.lt, if_eq=False, if_long_a=False)
def __add__(self, other):
"""BREAKS laziness and returns a plain-list"""
return list(self) + other
def __mul__(self, factor):
"""BREAKS laziness and returns a plain-list"""
return list(self) * factor
__radd__ = __add__
__rmul__ = __mul__
Обратите внимание, что этот класс также обсуждается в этом SO.
while(list.length < 30) if(i*i % 3 == 0) list += i++;
, либо выразительность (но при этом отбрасываете огромные ненужные списки) -list1 = range 0..10000; for i in list1 if i * i % 3 == 0 list2.add i; list2.trim(30)
. С ленивым вы пишете что-то похожее на последнее, но получаете производительность первого. Дэниел Тао блестяще объяснил это. - person Rob Grant   schedule 14.08.2014range().filter(var where var*var % 3 == 0).take(30)
- он написан очень выразительно, но код не запускается императивно. Выполняется:range
генерирует 1.Filter
- не выполняется из-за1*1%3 != 0
. Затемrange
генерирует 2.Filter
не работает. Затемrange
генерирует 3.Filter
проходит, потому что3*3%3==0
.Take
ставит его в очередь как ответ. После того, какtake
наберет 30, алгоритм останавливается. Он использует ровно столько памяти, сколько нужно, но это также хорошо читаемый (и распараллеливаемый) код. - person Rob Grant   schedule 14.08.2014