懒惰评估地图

时间:2016-05-24 14:58:32

标签: python lazy-evaluation map-function

我最近读到,Python 3中map的一个好处是它很懒。这意味着,最好做

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是懒惰的呢?

3 个答案:

答案 0 :(得分:6)

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

map 意味着任何函数f一起使用。特别是函数可能具有共享/全局状态,这已经失去了在不执行100个函数调用的情况下能够执行map(f, something)[100]的任何机会。不这样做会破坏结果的正确性

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

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

如果map渴望它会消耗两倍iterable的内存来进行循环,而懒惰map所需的唯一空间基本上是x的{​​{1}}

此外,它允许在无限迭代上调用map,如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.

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

  1. 如果sequence实际上是iterable来获取第n个元素,则必须使用前n个元素。之后,您必须将它们作为序列存储在您的班级中以备将来使用。但这已经打败了整个事情的目的,因为做PureMap(f, sequence)[1000]要求你将1000元素存储在内存中,即使它避免999调用f

  2. 您希望避免在同一元素上多次调用f。这意味着您还必须跟踪哪些元素已经计算过,哪些元素已经计算出来。

  3. 您可以实现目标的唯一情况如下:

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

    当满足所有这些假设时,您可以拥有一个“像range一样工作”的地图对象。

答案 1 :(得分:1)

首先,请注意range(Python 2中的xrange)是一种特殊情况。它不是一个简单的生成器,也不会返回一个列表。它也支持in操作,这不是迭代或迭代器的标准功能。

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

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

并非每个用例都需要随机访问,而那些必须完全实例化iterable或使用itertools之类的其他islice函数。

答案 2 :(得分:1)

有很多好处;例如,它可以更容易地编写内存有效的代码。

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)))

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