在Python中,是否可能对列表进行惰性求值?
例如:
如果列表被设置为惰性评估,则最终行将是[2]。
例如:
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]
如果列表被设置为惰性评估,则最终行将是[2]。
a = 1
list = [a]
print list
#[1]
a = 2
print list
#[1]
"懒惰"求值的概念通常与函数式语言相关联--但在这些语言中,您不能将两个不同的值重新分配给同一标识符,因此即使是在那里也不能重现您的示例。
关键点根本不是懒惰——它是确保使用标识符与获取标识符引用的相同值是相同的,而将标识符、一个裸名称,重新分配给另一个值,则保证使该标识符从此时起引用到不同的对象。第一个值(对象)的引用并没有丢失。
考虑一个类似的例子,其中不涉及向裸名称进行重新分配,而是涉及任何其他种类的突变(对于可变对象,当然——数字和字符串是不可变的),包括对与裸名称以外的东西进行赋值:
>>> a = [1]
>>> list = [a]
>>> print list
[[1]]
>>> a[:] = [2]
>>> print list
[[2]]
由于没有对裸名称a进行重新分配的a-...
,而是使用a[:] = ...
来重新分配a的内容,因此可以轻松地使Python变得像您想要的那样“懒惰”(实际上需要一些努力才能使其“渴望”!)...如果懒惰与渴望与这些情况有任何关系(事实并非如此;-)。
只需了解“给裸名称赋值”的完全简单的语义(与赋值给其他任何东西相比,可以通过适当使用自己的类型进行各种微调和控制),并且“懒惰 vs 渴望”的视觉错觉可能会希望消失;-)
当我寻找一个真正的惰性列表实现时,遇到了这篇帖子,但是它听起来像是一个有趣的尝试和解决方案。
下面的实现基本上是最初要求的内容:
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__
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 Grantrange().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个元素之后,算法停止。它只使用所需的内存,而且代码易于阅读(也易于并行化)。 - Rob Grant