懒惰评估地图

发布于 2021-01-29 18:01:29

我最近读到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惰性是重要的呢?

关注者
0
被浏览
45
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    您在这里比较苹果和桔子。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.
    

    但是,即使在这种情况下,您仍然会遇到一些问题:

    1. 如果sequence实际上是iterable,则要获取第n个元素,则必须消耗前n个元素。之后,您必须将它们作为序列存储在类中,以备将来使用。但这已经违反了整件事的目的,因为这样做PureMap(f, sequence)[1000]需要您始终将1000元素存储在内存中, 即使它避免了对的999调用f

    2. 您要避免f在同一元素上多次调用。这意味着您还必须跟踪已计算出哪个元素,而未计算出哪个元素。

    可以实现所需目标的唯一情况如下:

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

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



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看