“元素”存储在生成器中的哪个位置?

时间:2017-06-18 10:15:32

标签: python generator language-concepts

以下代码汇总了 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

我理解如何创建生成器函数以及为什么要使用它们但我不明白它们是如何工作的。

4 个答案:

答案 0 :(得分:5)

.test > td { position:relative; } 不存储值,您需要将生成器视为具有上下文的函数,它将每次保存状态和值generator它被要求这样做,所以,它给你一个值,然后“丢弃”它,保持计算的上下文,并等到你要求更多;并且将在生成上下文耗尽之前执行此操作

GENERATE

在您提供的示例中,使用的“唯一”内存是def firstn(n): num = 0 while num < n: yield num num += 1 ,是存储计算的位置,num生成器将firstn保存在num中虽然循环`已经完成。

答案 1 :(得分:1)

此示例可帮助您了解项目的计算方式和时间:

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

您的下一个价值是按需计算的;并非所有可能的值都需要在内存中。

答案 2 :(得分:1)

我认为你的第一个和第二个功能/方法在幕后做的真实例子将会有所帮助,你会更好地理解将要发生的事情。

让我们使用locals()处理每个函数/方法时打印隐藏的Python:

  

locals():更新并返回表示当前的字典   本地符号表。自由变量由locals()返回   在函数块中调用,但不在类块中调用。

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

因此,您可以看到第二个函数/方法(您的生成器)不关心过去或下一个过程的结果。此函数仅记住最后一个值(打破while循环的条件)并按需生成结果。

但是,在您的第一个示例中,您的函数/方法需要存储并记住每个步骤以及用于停止while循环然后返回最终结果的值...这使得该过程与您的生成器相比非常长。

答案 3 :(得分:0)

如果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个数字的数组少得多的空间。正如其他人所指出的那样,生成器本身只是存储当前状态,并在使用next调用时从那里继续计算,因此只需要在示例中存储nnum