是否可以在 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]
“惰性”评估的概念通常伴随着函数式语言——但在那些你不能将两个不同的值重新分配给同一个标识符的语言中,所以即使你的例子也不能被复制。
重点根本不是懒惰——而是保证使用标识符与获取对标识符所引用的相同值的引用相同,并将标识符(裸名)重新分配给不同的值,保证使标识符引用与它们不同的值。对第一个值(对象)的引用不会丢失。
考虑一个类似的例子,其中没有重新分配给裸名,而是任何其他类型的突变(对于可变对象,当然 - 数字和字符串是不可变的),包括分配给其他东西而不是裸名姓名:
>>> 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并不是很懒惰。
您可以使用生成器来模拟惰性数据结构(如无限列表等),但就使用普通列表语法等而言,您不会有惰性。
这是一个只读惰性列表,它只需要一个预定义的长度和一个缓存更新函数:
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中讨论。