Python, ленивый список

Возможно ли ленивое вычисление списка в Python?

Например

a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]

Если бы список был настроен на ленивое вычисление, то последняя строка была бы [2]


person Mike    schedule 08.03.2010    source источник
comment
Это не то, что я понимал под ленивой оценкой. Я бы сказал, что это больше похоже на это: мы хотим найти первые 30 положительных чисел, квадрат которых делится на 3. С нетерпеливым языком вы либо кодируете производительность - 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.2014
comment
Ленивый способ может выглядеть так: range().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


Ответы (4)


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

Дело вовсе не в лени - дело в том, что использование идентификатора гарантированно идентично получению ссылки на то же значение, на которое ссылается идентификатор, и повторному назначению идентификатора, < strong> голое имя с другим значением гарантированно заставит идентификатор ссылаться на другое значение, отличное от них. Ссылка на первое значение (объект) не теряется.

Рассмотрим аналогичный пример, в котором повторное присвоение пустому имени не используется, а скорее используется любой другой вид мутации (для изменяемого объекта, конечно - числа и строки неизменны), включая присвоение чему-то еще чем простое имя:

>>> a = [1]
>>> list = [a]
>>> print list
[[1]]
>>> a[:] = [2]
>>> print list
[[2]]

Поскольку нет a - ..., который переназначает простое имя a, а скорее a[:] = ..., который переназначает содержимое a, тривиально легко сделать Python настолько "ленивым", насколько вы хотите (и действительно, потребуются некоторые усилия, чтобы сделайте это «нетерпеливым»! -) ... если лень против рвения имеет какое-либо отношение к любому из этих случаев (а это не так ;-).

Просто имейте в виду совершенно простую семантику «присвоения простому имени» (по сравнению с назначением чему-либо еще, которое можно по-разному настраивать и контролировать, используя ваши собственные типы соответствующим образом), и можно надеяться, что оптическая иллюзия «ленивый против нетерпеливого» исчезнуть ;-)

person Alex Martelli    schedule 08.03.2010

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

Следующая реализация делает в основном то, о чем изначально просили:

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]

Это явно ужасно.

person Jamie Cockburn    schedule 07.12.2016
comment
Все еще интересно. Спасибо - person sehe; 21.06.2018

Python вообще не очень ленив.

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

person Amber    schedule 08.03.2010

Это отложенный список только для чтения, для которого требуется только предварительно определенная 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.

person ankostis    schedule 20.07.2021