开发者

Python, lazy list

开发者 https://www.devze.com 2022-12-22 09:24 出处:网络
Is it possible to have a list be evaluated lazily in Python? For exa开发者_JAVA技巧mple a = 1 list = [a]

Is it possible to have a list be evaluated lazily in Python?

For exa开发者_JAVA技巧mple

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

If the list was set to evaluate lazily then the final line would be [2]


The concept of "lazy" evaluation normally comes with functional languages -- but in those you could not reassign two different values to the same identifier, so, not even there could your example be reproduced.

The point is not about laziness at all -- it is that using an identifier is guaranteed to be identical to getting a reference to the same value that identifier is referencing, and re-assigning an identifier, a bare name, to a different value, is guaranteed to make the identifier refer to a different value from them on. The reference to the first value (object) is not lost.

Consider a similar example where re-assignment to a bare name is not in play, but rather any other kind of mutation (for a mutable object, of course -- numbers and strings are immutable), including an assignment to something else than a bare name:

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

Since there is no a - ... that reassigns the bare name a, but rather an a[:] = ... that reassigns a's contents, it's trivially easy to make Python as "lazy" as you wish (and indeed it would take some effort to make it "eager"!-)... if laziness vs eagerness had anything to do with either of these cases (which it doesn't;-).

Just be aware of the perfectly simple semantics of "assigning to a bare name" (vs assigning to anything else, which can be variously tweaked and controlled by using your own types appropriately), and the optical illusion of "lazy vs eager" might hopefully vanish;-)


Came across this post when looking for a genuine lazy list implementation, but it sounded like a fun thing to try and work out.

The following implementation does basically what was originally asked for:

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())

You use it like this:

>>> a = 1
>>> l = LazyClosureSequence(lambda: [a])
>>> print l
[1]
>>> a = 2
>>> print l
[2]

This is obviously horrible.


Python is not really very lazy in general.

You can use generators to emulate lazy data structures (like infinite lists, et cetera), but as far as things like using normal list syntax, et cetera, you're not going to have laziness.


That is a read-only lazy list where it only needs a pre-defined length and a cache-update function:

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__


Note that this class is discussed also in this SO.

0

精彩评论

暂无评论...
验证码 换一张
取 消