9

是否可以在 Python 中懒惰地评估列表?

例如

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

如果列表设置为延迟评估,那么最后一行将是 [2]

4

4 回答 4

11

“惰性”评估的概念通常伴随着函数式语言——但在那些你不能将两个不同的值重新分配给同一个标识符的语言中,所以即使你的例子也不能被复制。

重点根本不是懒惰——而是保证使用标识符与获取对标识符所引用的相同值的引用相同,并将标识符(裸名)重新分配给不同的值,保证使标识符引用与它们不同的值。对第一个值(对象)的引用不会丢失。

考虑一个类似的例子,其中没有重新分配给裸名,而是任何其他类型的突变(对于可变对象,当然 - 数字和字符串是不可变的),包括分配给其他东西而不是裸名姓名:

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

由于没有a - ...重新分配裸名称a,而是a[:] = ...重新分配a内容,因此很容易使 Python 像您希望的那样“懒惰”(实际上要使其“急切”!-)。 ..如果懒惰与渴望与这些情况中的任何一种有关(它没有;-)。

请注意“分配给一个裸名”的完全简单的语义(与分配给其他任何东西,可以通过适当地使用您自己的类型进行各种调整和控制),并且“懒惰与渴望”的视觉错觉可能希望消失;-)

于 2010-03-08T04:06:21.693 回答
11

在寻找真正的惰性列表实现时遇到了这篇文章,但尝试和解决听起来很有趣。

以下实现基本上完成了最初的要求:

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]

这显然很可怕。

于 2016-12-07T11:00:04.130 回答
8

总的来说, Python并不是很懒惰

您可以使用生成器来模拟惰性数据结构(如无限列表等),但就使用普通列表语法等而言,您不会有惰性。

于 2010-03-08T03:55:39.997 回答
0

这是一个只读惰性列表,它只需要一个预定义的长度和一个缓存更新函数:

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中讨论。

于 2021-07-20T00:33:33.257 回答