懒惰评估地图
我最近读到map
了Python 3的一个好处是它很懒。那就更好了
map(lambda x: x**2, range(10**100))
而不是
[x**2 for x in range(10**100)]
我很好奇的是如何使用这种懒惰。如果生成映射对象,例如,如何访问生成的操作/列表中的特定元素。在map
我所见过的几乎所有文档中,他们都会做类似print(map(...))
或的事情for
i in map(...)
(据我所知),它放弃了惰性概念,因为它隐式将地图转换为列表。
我想我正在寻找的是能够以与range
我可以懒惰地懒惰x =
range(10**100)
地生成地图对象类似的方式使用地图对象的能力,并且可以在x[10000]
没有巨大计算量的情况下懒惰地生成地图对象。
如果这个概念不存在,那么map
懒惰有什么好处?如果您始终需要将其转换为某些非惰性对象(如列表),那么为什么map
惰性是重要的呢?
-
您在这里比较苹果和桔子。
range
是 不是
只是一个懒惰的迭代。它是一个特定的对象,其内容满足特定的法律,该法律允许支持许多操作而无需在内存中实际构建巨大的序列。这是因为的第n个元素range
基本上只是start + n*step
(模stop
,符号等)但是
map
它 意味着 可以使用 任何 功能f
。特别是功能可能具有共享/全局状态,这已经失去了在map(f, something)[100]
不执行100个功能调用的情况下能够执行的任何机会。不这样做会破坏结果的 正确性 。map
“懒惰”只是意味着它不会立即生成完整的结果列表,而是等待您要求下一个结果,然后再调用f
并产生它。这样可以避免在代码中生成不必要的列表,例如:for x in map(f, iterable): # do something with x
如果
map
急切的话iterable
,执行循环将消耗两倍的内存,而懒惰map
的话,所需的唯一空间x
基本上是。此外,它可以调用
map
的 无限iterables
一样count()
。这显然导致程序永无休止地做某事,或者在某个时候您可以停止调查map
。渴望map
不能处理这种情况。如果您想使用仅适用于纯功能并且允许随机访问的受限映射,则可以编写自己的类:
class PureMap: def __init__(self, function, sequence): self._f = function self._sequence = sequence def __iter__(self): return map(self._f, self._sequence) def __getitem__(self, i): return self._f(self._sequence[i]) # etc.
但是,即使在这种情况下,您仍然会遇到一些问题:
-
如果
sequence
实际上是iterable
,则要获取第n个元素,则必须消耗前n个元素。之后,您必须将它们作为序列存储在类中,以备将来使用。但这已经违反了整件事的目的,因为这样做PureMap(f, sequence)[1000]
需要您始终将1000
元素存储在内存中, 即使它避免了对的999
调用f
。 -
您要避免
f
在同一元素上多次调用。这意味着您还必须跟踪已计算出哪个元素,而未计算出哪个元素。
可以实现所需目标的唯一情况如下:
- 被调用的函数是纯函数
- 可迭代的参数类似于这样
range
,它允许随机访问而不必产生其他元素 - 您调用的函数很快,因此您可以在各种元素上重新计算它,而不必过多担心性能。
当所有这些假设都满足时,您可以拥有一个“像
range
”一样工作的地图对象。 -