地图的惰性评估
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.
然而即使在这种情况下你也有一些问题:
如果sequence
实际上是一个iterable
要获得第n个元素你必须消耗前n个元素。之后,您必须将它们作为序列存储在 class 中以备将来使用。但这已经违背了整个事情的目的,因为 PureMap(f, sequence)[1000]
无论如何都需要你在内存中存储 1000
元素, 即使它避免了 999
对 [=17 的调用=].
您想避免对同一元素多次调用 f
。这意味着您还必须跟踪哪些元素已经计算过,哪些没有。
唯一可以达到你想要的效果的情况如下:
- 被调用的函数是纯函数
- 可迭代参数类似于
range
允许随机访问而无需生成其他元素
- 您调用的函数速度很快,因此您可以在各种元素上重新计算它,而不必太担心性能。
当满足所有这些假设时,您可以拥有一个 "works like range
".
的地图对象
我最近在 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.
然而即使在这种情况下你也有一些问题:
如果
sequence
实际上是一个iterable
要获得第n个元素你必须消耗前n个元素。之后,您必须将它们作为序列存储在 class 中以备将来使用。但这已经违背了整个事情的目的,因为PureMap(f, sequence)[1000]
无论如何都需要你在内存中存储1000
元素, 即使它避免了999
对 [=17 的调用=].您想避免对同一元素多次调用
f
。这意味着您还必须跟踪哪些元素已经计算过,哪些没有。
唯一可以达到你想要的效果的情况如下:
- 被调用的函数是纯函数
- 可迭代参数类似于
range
允许随机访问而无需生成其他元素 - 您调用的函数速度很快,因此您可以在各种元素上重新计算它,而不必太担心性能。
当满足所有这些假设时,您可以拥有一个 "works like range
".