地图的惰性评估

Lazy evaluation of map

我最近在 Python 3 中读到 map 的一个好处是它很懒惰。也就是说,最好做

map(lambda x: x**2, range(10**100))

而不是

[x**2 for x in range(10**100)]

我很好奇的是如何利用这种懒惰。例如,如果我生成地图对象,我该如何访问生成的 operation/list 中的特定元素。在我见过的几乎所有关于 map 的文档中,他们都会做类似 print(map(...))for i in map(...) 的事情(据我所知)放弃了惰性概念,因为它隐式转换了地图到列表。

我想我正在寻找的是能够以与 range 类似的惰性方式使用地图对象,我可以在其中执行 x = range(10**100) 并懒惰地生成 x[10000] 而无需大量计算负载。

如果不存在这个概念,那么map懒惰有什么好处呢?如果你总是需要将它转换为一些非惰性对象,如列表,为什么 map 是惰性的很重要?

首先,请注意range(Python 2 中的xrange)是一个特例。它不是一个简单的生成器,也不只是 return 一个列表。它还支持 in 操作,这不是可迭代对象或迭代器的标准功能。

考虑一下 map(func, iterable) 可以在无限可迭代对象上调用,或者在获取下一个值的过程是一个耗时过程的可迭代对象上调用。

您需要注意您的函数可能会处理这些类型的值,并确保使用惰性函数,如 itertools.imap 否则。由于基本上不可能确定迭代器是无限的,即使在运行时,内置函数不应该在最广泛的输入范围内正确运行吗?

并非每个用例都需要随机访问,那些需要随机访问的用例必须完全实例化可迭代对象或使用另一个 itertools 函数,例如 islice.

有很多好处;例如,它可以更轻松地编写内存高效代码。

def take_up_a_lot_of_memory(*args):
    """
    A contrived example of a function that uses a lot of memory
    """
    return sum([i ** 2 for i in range(10 ** 6)])

megasum = sum(map(take_up_a_lot_of_memory, range(1000)))

此外,有时您可以提前终止计算而不遍历所有地图结果,从而避免冗余。

你在这里比较苹果和橘子。 range 而不是 只是一个惰性可迭代对象。它是一个特定的对象,其内容满足特定的规则,允许支持许多操作,而无需在内存中实际构建一个巨大的序列。那是因为 range 的第 n 个元素基本上只是 start + n*step(取模 stop,符号等)

但是 map 意味着any 函数 f 一起工作。特别是函数可能具有 shared/global 状态,这已经破坏了在不执行 100 次函数调用的情况下能够执行 map(f, something)[100] 的任何机会。不这样做会破坏结果的正确性

map 是惰性的,只是意味着它不会立即构建完整的结果列表,而是等待您在调用 f 并生成它之前要求下一个结果。这避免在代码中构建不必要的列表,例如:

for x in map(f, iterable):
    # do something with x

如果 map 很急,它将消耗 iterable 的两倍内存来执行循环,对于惰性 map 唯一需要的 space x基本上。

此外,它允许在 无限迭代器 上调用 map,例如 count()。这显然会导致一个永无止境的程序在做某事,或者在某个时候你可以停止查看 map。急切的 map 无法处理这种情况。

如果您想使用自己的仅适用于纯函数且允许随机访问的受限映射,您可以编写自己的 class:

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.

然而即使在这种情况下你也有一些问题:

  1. 如果sequence实际上是一个iterable要获得第n个元素你必须消耗前n个元素。之后,您必须将它们作为序列存储在 class 中以备将来使用。但这已经违背了整个事情的目的,因为 PureMap(f, sequence)[1000] 无论如何都需要你在内存中存储 1000 元素, 即使它避免了 999 对 [=17 的调用=].

  2. 您想避免对同一元素多次调用 f。这意味着您还必须跟踪哪些元素已经计算过,哪些没有。

唯一可以达到你想要的效果的情况如下:

  • 被调用的函数是纯函数
  • 可迭代参数类似于 range 允许随机访问而无需生成其他元素
  • 您调用的函数速度很快,因此您可以在各种元素上重新计算它,而不必太担心性能。

当满足所有这些假设时,您可以拥有一个 "works like range".

的地图对象