'elements' 存储在生成器中的什么位置?
Where are the 'elements' being stored in a generator?
下面的代码总结了all_numbers中列表中的所有数字。这是有道理的,因为所有要汇总的数字都在列表中。
def firstn(n):
'''Returns list number range from 0 to n '''
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
return nums
# all numbers are held in a list which is memory intensive
all_numbers = firstn(100000000)
sum_of_first_n = sum(all_numbers)
# Uses 3.8Gb during processing and 1.9Gb to store variables
# 13.9 seconds to process
sum_of_first_n
将上述函数转换为生成器函数时,我发现使用更少的内存得到了相同的结果(代码下方)。我不明白的是,如果 all_numbers 不包含上面列表中的所有数字,怎么能总结呢?
如果数字是按需生成的,那么人们会生成所有数字并将它们汇总在一起,那么这些数字存储在哪里,这如何转化为减少内存使用量?
def firstn(n):
num = 0
while num < n:
yield num
num += 1
# all numbers are held in a generator
all_numbers = firstn(100000000)
sum_of_first_n = sum(all_numbers)
# Uses < 100Mb during processing and to store variables
# 9.4 seconds to process
sum_of_first_n
我知道如何创建生成器函数以及为什么要使用它们,但我不明白它们是如何工作的。
此示例可以帮助您了解计算项目的方式和时间:
def firstn(n):
num = 0
while num < n:
yield num
print('incrementing num')
num += 1
gen = firstn(n=10)
a0 = next(gen)
print(a0) # 0
a1 = next(gen) # incrementing num
print(a1) # 1
a2 = next(gen) # incrementing num
print(a2) # 2
该函数没有 return
,但它保持其内部状态(堆栈框架)并从上次 yield
编辑的点继续。
for
循环只是重复调用 next
。
您的下一个值是按需计算的;当时并非所有可能的值都需要在内存中。
A generator
不存储值,您需要 将生成器视为具有上下文 的函数,它将保存状态 GENERATE
每次被要求这样做时的值,所以,它给你一个值,然后 "discard" 它, 保持计算的上下文 并等到你要求更多的;并且 将这样做,直到生成器上下文耗尽 。
def firstn(n):
num = 0
while num < n:
yield num
num += 1
在您提供的这个示例中,使用的 "only" 内存是 num
,是存储计算的地方,firstn
生成器将 num
保存在其 context
直到 while loop
完成。
如果sum
-函数写在Python中,它可能类似于:
def sum(iterable, start=0):
part_sum = start
for element in iterable:
part_sum += element
return part_sum
(当然这个函数和真正的函数有很多不同sum
,但是你的例子的工作方式非常相似。)
如果用生成器调用sum(all_numbers)
,变量element
只存储当前元素,变量part_sum
只存储当前元素之前所有数字的总和.这样就可以只使用两个变量来计算总和,这显然比存储所有 100000000 个数字的数组需要的 space 少得多。正如其他人所指出的那样,生成器本身只是存储它的当前状态并在使用 next
调用时从那里继续计算,因此只需要在您的示例中存储 n
和 num
。
我认为您的第一个和第二个 functions/methods 在幕后所做的真实示例会有所帮助,您会更好地理解发生了什么。
让我们在使用 locals()
处理每个 function/method 时打印隐藏的 Python :
locals(): Update and return a dictionary representing the current
local symbol table. Free variables are returned by locals() when it is
called in function blocks, but not in class blocks.
>>> def firstn(n):
'''Returns list number range from 0 to n '''
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
print(locals())
return nums
>>> firstn(10)
将打印:
{'nums': [0], 'n': 10, 'num': 1}
{'nums': [0, 1], 'n': 10, 'num': 2}
{'nums': [0, 1, 2], 'n': 10, 'num': 3}
{'nums': [0, 1, 2, 3], 'n': 10, 'num': 4}
{'nums': [0, 1, 2, 3, 4], 'n': 10, 'num': 5}
{'nums': [0, 1, 2, 3, 4, 5], 'n': 10, 'num': 6}
{'nums': [0, 1, 2, 3, 4, 5, 6], 'n': 10, 'num': 7}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7], 'n': 10, 'num': 8}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8], 'n': 10, 'num': 9}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'n': 10, 'num': 10}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
但是:
>>> def firstn(n):
num = 0
while num < n:
yield num
num += 1
print(locals())
>>> list(firstn(10))
将打印:
{'n': 10, 'num': 1}
{'n': 10, 'num': 2}
{'n': 10, 'num': 3}
{'n': 10, 'num': 4}
{'n': 10, 'num': 5}
{'n': 10, 'num': 6}
{'n': 10, 'num': 7}
{'n': 10, 'num': 8}
{'n': 10, 'num': 9}
{'n': 10, 'num': 10}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
因此,如您所见,第二个 function/method(您的生成器)不关心过去或下一个过程的结果。此函数仅记住最后一个值(中断 while 循环的条件)并按需生成结果。
但是,在您的第一个示例中,您的 function/method 需要存储并记住每一步以及用于停止 while 循环然后返回最终结果的值...这使得过程非常长相比到你的发电机。
下面的代码总结了all_numbers中列表中的所有数字。这是有道理的,因为所有要汇总的数字都在列表中。
def firstn(n):
'''Returns list number range from 0 to n '''
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
return nums
# all numbers are held in a list which is memory intensive
all_numbers = firstn(100000000)
sum_of_first_n = sum(all_numbers)
# Uses 3.8Gb during processing and 1.9Gb to store variables
# 13.9 seconds to process
sum_of_first_n
将上述函数转换为生成器函数时,我发现使用更少的内存得到了相同的结果(代码下方)。我不明白的是,如果 all_numbers 不包含上面列表中的所有数字,怎么能总结呢?
如果数字是按需生成的,那么人们会生成所有数字并将它们汇总在一起,那么这些数字存储在哪里,这如何转化为减少内存使用量?
def firstn(n):
num = 0
while num < n:
yield num
num += 1
# all numbers are held in a generator
all_numbers = firstn(100000000)
sum_of_first_n = sum(all_numbers)
# Uses < 100Mb during processing and to store variables
# 9.4 seconds to process
sum_of_first_n
我知道如何创建生成器函数以及为什么要使用它们,但我不明白它们是如何工作的。
此示例可以帮助您了解计算项目的方式和时间:
def firstn(n):
num = 0
while num < n:
yield num
print('incrementing num')
num += 1
gen = firstn(n=10)
a0 = next(gen)
print(a0) # 0
a1 = next(gen) # incrementing num
print(a1) # 1
a2 = next(gen) # incrementing num
print(a2) # 2
该函数没有 return
,但它保持其内部状态(堆栈框架)并从上次 yield
编辑的点继续。
for
循环只是重复调用 next
。
您的下一个值是按需计算的;当时并非所有可能的值都需要在内存中。
A generator
不存储值,您需要 将生成器视为具有上下文 的函数,它将保存状态 GENERATE
每次被要求这样做时的值,所以,它给你一个值,然后 "discard" 它, 保持计算的上下文 并等到你要求更多的;并且 将这样做,直到生成器上下文耗尽 。
def firstn(n):
num = 0
while num < n:
yield num
num += 1
在您提供的这个示例中,使用的 "only" 内存是 num
,是存储计算的地方,firstn
生成器将 num
保存在其 context
直到 while loop
完成。
如果sum
-函数写在Python中,它可能类似于:
def sum(iterable, start=0):
part_sum = start
for element in iterable:
part_sum += element
return part_sum
(当然这个函数和真正的函数有很多不同sum
,但是你的例子的工作方式非常相似。)
如果用生成器调用sum(all_numbers)
,变量element
只存储当前元素,变量part_sum
只存储当前元素之前所有数字的总和.这样就可以只使用两个变量来计算总和,这显然比存储所有 100000000 个数字的数组需要的 space 少得多。正如其他人所指出的那样,生成器本身只是存储它的当前状态并在使用 next
调用时从那里继续计算,因此只需要在您的示例中存储 n
和 num
。
我认为您的第一个和第二个 functions/methods 在幕后所做的真实示例会有所帮助,您会更好地理解发生了什么。
让我们在使用 locals()
处理每个 function/method 时打印隐藏的 Python :
locals(): Update and return a dictionary representing the current local symbol table. Free variables are returned by locals() when it is called in function blocks, but not in class blocks.
>>> def firstn(n):
'''Returns list number range from 0 to n '''
num, nums = 0, []
while num < n:
nums.append(num)
num += 1
print(locals())
return nums
>>> firstn(10)
将打印:
{'nums': [0], 'n': 10, 'num': 1}
{'nums': [0, 1], 'n': 10, 'num': 2}
{'nums': [0, 1, 2], 'n': 10, 'num': 3}
{'nums': [0, 1, 2, 3], 'n': 10, 'num': 4}
{'nums': [0, 1, 2, 3, 4], 'n': 10, 'num': 5}
{'nums': [0, 1, 2, 3, 4, 5], 'n': 10, 'num': 6}
{'nums': [0, 1, 2, 3, 4, 5, 6], 'n': 10, 'num': 7}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7], 'n': 10, 'num': 8}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8], 'n': 10, 'num': 9}
{'nums': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'n': 10, 'num': 10}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
但是:
>>> def firstn(n):
num = 0
while num < n:
yield num
num += 1
print(locals())
>>> list(firstn(10))
将打印:
{'n': 10, 'num': 1}
{'n': 10, 'num': 2}
{'n': 10, 'num': 3}
{'n': 10, 'num': 4}
{'n': 10, 'num': 5}
{'n': 10, 'num': 6}
{'n': 10, 'num': 7}
{'n': 10, 'num': 8}
{'n': 10, 'num': 9}
{'n': 10, 'num': 10}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
因此,如您所见,第二个 function/method(您的生成器)不关心过去或下一个过程的结果。此函数仅记住最后一个值(中断 while 循环的条件)并按需生成结果。
但是,在您的第一个示例中,您的 function/method 需要存储并记住每一步以及用于停止 while 循环然后返回最终结果的值...这使得过程非常长相比到你的发电机。