范围对象如何允许多次迭代而生成器对象不允许?

How does range object allow for multiple iterations while generators object do not?

我想知道 Python 中范围和生成器之间的区别。

我做了一些研究并找到了一些有用的答案,例如 and this one,这些答案解释了这些对象之间的差异,尽管它们可能 return 相似。

我想探索的差异之一是范围对象可以被多次调用而生成器对象不能。为了更清楚地向我自己证明这一点,我考虑了以下代码:

def my_range(first=0, last=3, step=1):
    number = first
    while number < last:
        yield number
        number+=step

a = range(0,3)
b = my_range()

for i in a:
    print(i)
print("First")
for i in a:
    print(i)
print("Second")
for i in b:
    print(i)
print("Third")
for i in b:
    print(i)
print("Fourth")

输出:

0
1
2
First
0
1
2
Second
0
1
2
Third
Fourth

我很清楚生成器会“用完”,而范围不会。但是我无法准确找到在源代码中的什么位置(以及源代码本身的位置)定义了这种行为。我不确定从哪里开始查找和解释指示范围对象可以多次使用但生成器对象不能的代码。

我希望得到帮助以查找和理解 属性 如何在 Python 的源代码中实现一个对象可以迭代多少次。

yield 语句暂停函数的执行并将值发送回调用者,但保留足够的状态以使函数能够从停止的地方恢复。恢复时,函数会在最后一次 yield 运行 之后立即继续执行。这允许其代码随着时间的推移产生一系列值,而不是立即计算它们并将它们像列表一样发回。

所以第四个循环将继续您的 my_range 函数,其中存储的号码是上次调用时存储的号码 = 3

范围对象是一个普通的可迭代序列,而生成器也是一个迭代器。

两者之间的区别在于,可迭代对象用于生成存储迭代状态的迭代器。如果我们尝试一下 range、它的迭代器和 next,就可以看出这一点。

首先,如果我们尝试在其上调用 next,我们可以看到 range 不是迭代器

In [1]: next(range(0))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [1], in <module>
----> 1 next(range(0))

TypeError: 'range' object is not an iterator

我们可以通过调用 iter 内置函数来自己创建迭代器,我们可以看到,当在我们的范围上调用时,这会为我们提供不同的迭代器类型。

In [2]: iter(range(0))
Out[2]: <range_iterator at 0x28573eabc90>

由 iterable 创建的每个迭代器都将存储自己的迭代状态(例如,范围对象的索引,每次前进时都会递增),因此我们可以独立使用它们

In [3]: range_obj = range(10)

In [4]: iterator_1 = iter(range_obj)

In [5]: iterator_2 = iter(range_obj)

In [6]: [next(iterator_1) for _ in range(5)]  # advance iterator_1 5 times
Out[6]: [0, 1, 2, 3, 4]

In [7]: next(iterator_2)  # left unchanged, fetches first item from range_obj
Out[7]: 0

Python 在使用 for 循环时也会自己创建迭代器,如果我们看一下它的指令生成器就可以看出这一点

In [8]: dis.dis("for a in b: ...")
  1           0 LOAD_NAME                0 (b)
              2 GET_ITER
        >>    4 FOR_ITER                 4 (to 10)
              6 STORE_NAME               1 (a)
              8 JUMP_ABSOLUTE            4
        >>   10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

这里的GET_ITERiter(b)是一样的。

现在有了生成器,在调用生成器函数创建它之后,Python 直接给你一个迭代器,因为它上面没有可生成的可迭代对象。调用生成器函数可以看作是调用 iter(...),但是传递给它的一切都留给了用户 作为函数的参数,而不是从创建它的对象中获取信息。