Python,懒惰列表

7
在Python中,是否可能对列表进行惰性求值?
例如:
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]

如果列表被设置为惰性评估,则最终行将是[2]。

2
这不是我理解的惰性求值。我认为更像是:我们要找到前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)。使用惰性求值,你会写出类似后者的代码,但获得前者的性能。Daniel Tao 讲解得非常好 - Rob Grant
一种懒惰的写法可能是这样的: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==0Take将它排队作为一个答案。在take获取30个元素之后,算法停止。它只使用所需的内存,而且代码易于阅读(也易于并行化)。 - Rob Grant
4个回答

11

"懒惰"求值的概念通常与函数式语言相关联--但在这些语言中,您不能将两个不同的值重新分配给同一标识符,因此即使是在那里也不能重现您的示例。

关键点根本不是懒惰——它是确保使用标识符与获取标识符引用的相同值是相同的,而将标识符、一个裸名称,重新分配给另一个值,则保证使该标识符从此时起引用到不同的对象。第一个值(对象)的引用并没有丢失。

考虑一个类似的例子,其中不涉及向裸名称进行重新分配,而是涉及任何其他种类的突变(对于可变对象,当然——数字和字符串是不可变的),包括对与裸名称以外的东西进行赋值:

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

由于没有对裸名称a进行重新分配的a-...,而是使用a[:] = ...来重新分配a内容,因此可以轻松地使Python变得像您想要的那样“懒惰”(实际上需要一些努力才能使其“渴望”!)...如果懒惰与渴望与这些情况有任何关系(事实并非如此;-)。

只需了解“给裸名称赋值”的完全简单的语义(与赋值给其他任何东西相比,可以通过适当使用自己的类型进行各种微调和控制),并且“懒惰 vs 渴望”的视觉错觉可能会希望消失;-)


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]

这显然是很糟糕的。


1
仍然有趣。谢谢。 - sehe

7

Python通常并不是非常懒惰。

你可以使用生成器来模拟惰性数据结构(如无限列表等),但是如果要使用正常的列表语法等,就不能实现惰性。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接