Python for 循环和迭代器行为

Python for loop and iterator behavior

我想了解更多关于iterators的知识,所以如果我错了,请纠正我。

迭代器是一个对象,它具有指向下一个对象的指针,并作为缓冲区或流(即链表)读取。它们特别高效,因为它们所做的只是通过引用而不是使用索引告诉您下一步是什么。

但是我仍然不明白为什么会发生以下行为:

In [1]: iter = (i for i in range(5))

In [2]: for _ in iter:
   ....:     print _
   ....:     
0
1
2
3
4

In [3]: for _ in iter:
   ....:     print _
   ....:     

In [4]: 

在迭代器 (In [2]) 的第一个循环之后,就好像它被消耗掉了并且留空了,所以第二个循环 (In [3]) 什么都不打印。

但是我从未为 iter 变量分配新值。

for 循环背后到底发生了什么?

您的猜测是正确的:迭代器已被消耗。

实际上,您的迭代器是一个 generator,它是一个可以迭代 一次的对象。

type((i for i in range(5))) # says it's type generator 

def another_generator():
    yield 1 # the yield expression makes it a generator, not a function

type(another_generator()) # also a generator

它们高效的原因与告诉你下一步是什么无关"by reference."它们之所以高效,是因为它们只根据请求生成下一个项目;所有项目都不是一次生成的。事实上,你可以拥有一个无限的发电机:

def my_gen():
    while True:
        yield 1 # again: yield means it is a generator, not a function

for _ in my_gen(): print(_) # hit ctl+c to stop this infinite loop!

一些其他更正以帮助提高您的理解:

  • 生成器不是指针,其行为也不像您在其他语言中所熟悉的那样。
  • 与其他语言的区别之一:如上所述,生成器的每个结果都是动态生成的。在请求之前不会产生下一个结果。
  • 关键字组合 for in 接受一个可迭代对象作为它的第二个参数。
  • 可迭代对象可以是生成器,如您的示例中所示,但它也可以是任何其他可迭代对象,例如 listdictstr 对象(字符串),或提供所需功能的用户定义类型。
  • iter function is applied to the object to get an iterator (by the way: don't use iter as a variable name in Python, as you have done - it is one of the keywords). Actually, to be more precise, the object's __iter__ method 被调用(在大多数情况下,所有 iter 函数都会执行;__iter__ 是 Python 的其中一个 -称为 "magic methods").
  • 如果调用__iter__成功,函数next() is applied to the iterable object over and over again, in a loop, and the first variable supplied to for in is assigned to the result of the next() function. (Remember: the iterable object could be a generator, or a container object's iterator, or any other iterable object.) Actually, to be more precise: it calls the iterator object's __next__方法,也就是另一个"magic method"。
  • next() 引发 StopIteration 异常时,for 循环结束(这通常发生在调用 next() 时可迭代对象没有另一个对象要产生时) .

您可以 "manually" 在 python 中实现一个 for 循环(可能不完美,但足够接近):

try:
    temp = iterable.__iter__()
except AttributeError():
    raise TypeError("'{}' object is not iterable".format(type(iterable).__name__))
else:
    while True:
        try:
            _ = temp.__next__()
        except StopIteration:
            break
        except AttributeError:
            raise TypeError("iter() returned non-iterator of type '{}'".format(type(temp).__name__))
        # this is the "body" of the for loop
        continue

上面的示例代码与您的示例代码几乎没有区别。

实际上,for 循环中更有趣的部分不是 for,而是 in。单独使用 in 产生的效果与 for in 不同,但是理解 in 对其参数的作用非常有用,因为 for in 实现了非常相似的行为。

  • 单独使用时,in关键字首先调用对象的__contains__ method,这是另一个"magic method"(注意使用时跳过此步骤for in)。在容器上单独使用 in,你可以这样做:

    1 in [1, 2, 3] # True
    'He' in 'Hello' # True
    3 in range(10) # True
    'eH' in 'Hello'[::-1] # True
    
  • 如果可迭代对象不是容器(即它没有 __contains__ 方法),in 接下来会尝试调用对象的 __iter__方法。如前所述:__iter__ 方法 return 在 Python 中被称为 1 上的 iterator. Basically, an iterator is an object that you can use the built-in generic function next()。生成器只是迭代器的一种。

  • 如果调用__iter__成功,in关键字应用函数next() to the iterable object over and over again. (Remember: the iterable object could be a generator, or a container object's iterator, or any other iterable object.) Actually, to be more precise: it calls the iterator object's __next__方法)。
  • 如果对象没有return迭代器的__iter__方法,in然后使用对象的__getitem__ 方法2
  • 如果以上所有尝试都失败,您将得到 TypeError exception

如果您希望创建自己的对象类型进行迭代(即,您可以使用 for in,或仅使用 in),了解它很有用关于 yield 关键字,它在 generators 中使用(如上所述)。

class MyIterable():
    def __iter__(self):
        yield 1

m = MyIterable()
for _ in m: print(_) # 1
1 in m # True    

yield 的存在将函数或方法变成了生成器,而不是常规的 function/method。如果您使用生成器,则不需要 __next__ 方法(它会自动带来 __next__ )。

如果您希望创建自己的容器对象类型(即,您可以单独对其使用 in,但不能使用 for in),您只需要 __contains__方法。

class MyUselessContainer():
    def __contains__(self, obj):
        return True

m = MyUselessContainer()
1 in m # True
'Foo' in m # True
TypeError in m # True
None in m # True

1 请注意,要成为迭代器,对象必须实现 the iterator protocol. This only means that both the __next__ and __iter__ methods must be correctly implemented (generators come with this functionality "for free", so you don't need to worry about it when using them). Also note that the ___next__ method is actually next (no underscores) in Python 2

2 请参阅 this answer 了解创建可迭代对象 类.

的不同方法

For 循环基本上调用应用于对象的 next 方法 (__next__ in Python 3).

你可以简单地模拟这个:

iter = (i for i in range(5))

print(next(iter))
print(next(iter))  
print(next(iter))  
print(next(iter))  
print(next(iter)) 

# this prints 1 2 3 4 

此时输入对象中没有下一个元素。所以这样做:

print(next(iter))  

将导致抛出 StopIteration 异常。此时for就会停止。迭代器可以是 any object ,它将响应 next() 函数并在没有更多元素时抛出异常。它不必是任何指针或引用(在 C/C++ 意义上无论如何 python 中没有这样的东西)、链表等

python 中有一个迭代器协议,它定义了 for 语句如何处理列表和字典,以及其他可以循环的东西。

它在 python 文档中 here and here

迭代器协议通常以 python 生成器的形式工作。我们yield一个值只要我们有一个值直到我们到达终点然后我们提高StopIteration

所以让我们编写自己的迭代器:

def my_iter():
    yield 1
    yield 2
    yield 3
    raise StopIteration()

for i in my_iter():
    print i

结果是:

1
2
3

有几点需要注意。 my_iter 是一个函数。 my_iter() returns 一个迭代器。

如果我改用这样的迭代器编写:

j = my_iter()    #j is the iterator that my_iter() returns
for i in j:
    print i  #this loop runs until the iterator is exhausted

for i in j:
    print i  #the iterator is exhausted so we never reach this line

结果同上。当我们进入第二个 for 循环时,iter 已经耗尽。

但这太简单了,那么更复杂的事情呢?为什么不呢?

def capital_iter(name):
    for x in name:
        yield x.upper()
    raise StopIteration()

for y in capital_iter('bobert'):
    print y

而当它 运行s 时,我们在字符串类型上使用迭代器(内置于 iter)。反过来,这允许我们 运行 在其上进行 for 循环,并在完成之前产生结果。

B
O
B
E
R
T

所以现在这回避了问题,那么迭代器中的 yield 之间会发生什么?

j = capital_iter("bobert")
print i.next()
print i.next()
print i.next()

print("Hey there!")

print i.next()
print i.next()
print i.next()

print i.next()  #Raises StopIteration

答案是函数在 yield 处暂停,等待下一次调用 next()。

B
O
B
Hey There!
E
R
T
Traceback (most recent call last):
  File "", line 13, in 
    StopIteration

概念 1

All generators are iterators but all iterators are not generator

概念 2

An iterator is an object with a next (Python 2) or next (Python 3) method.

概念 3

Quoting from wiki Generators Generators functions allow you to declare a function that behaves like an iterator, i.e. it can be used in a for loop.

你的情况

>>> it = (i for i in range(5))
>>> type(it)
<type 'generator'>
>>> callable(getattr(it, 'iter', None))
False
>>> callable(getattr(it, 'next', None))
True

关于 iter()__getitem__ 类 缺少自己的 __iter__ 方法的行为的一些额外细节。


__iter__ 之前有 __getitem__。如果 __getitem__0 - len(obj)-1 中的 int 一起工作,那么 iter() 支持这些对象。它将构造一个新的迭代器,重复调用 __getitem__012...,直到它得到一个 IndexError,它转换为 StopIteration.

有关创建迭代器的不同方法的更多详细信息,请参阅 this answer

摘自the Python Practice book


5.迭代器和生成器

5.1。迭代器

我们使用 for 语句循环列表。

>>> for i in [1, 2, 3, 4]:
...     print i,
...
1
2
3
4

如果我们将它与字符串一起使用,它会遍历其字符。

>>> for c in "python":
...     print c
...
p
y
t
h
o
n

如果我们将它与字典一起使用,它会遍历它的键。

>>> for k in {"x": 1, "y": 2}:
...     print k
...
y
x

如果我们将它与文件一起使用,它会遍历文件的行。

>>> for line in open("a.txt"):
...     print line,
...
first line
second line

因此有许多类型的对象可以与 for 循环一起使用。这些被称为可迭代对象。

有许多函数使用这些可迭代对象。

>>> ",".join(["a", "b", "c"])
'a,b,c'
>>> ",".join({"x": 1, "y": 2})
'y,x'
>>> list("python")
['p', 'y', 't', 'h', 'o', 'n']
>>> list({"x": 1, "y": 2})
['y', 'x']

5.1.1。迭代协议

内置函数 iter 采用可迭代对象和 return 迭代器。

    >>> x = iter([1, 2, 3])
>>> x
<listiterator object at 0x1004ca850>
>>> x.next()
1
>>> x.next()
2
>>> x.next()
3
>>> x.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

停止迭代

每次我们在迭代器上调用 next 方法时都会为我们提供下一个元素。如果没有更多元素,它会引发 StopIteration。

迭代器实现为 类。这是一个像内置 xrange 函数一样工作的迭代器。

class yrange:
    def __init__(self, n):
        self.i = 0
        self.n = n

    def __iter__(self):
        return self

    def next(self):
        if self.i < self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

iter 方法使对象可迭代。在幕后,iter 函数调用给定对象的 iter 方法。

iter的return值是迭代器。它应该有一个 next 方法并在没有更多元素时引发 StopIteration。

让我们试试看:

>>> y = yrange(3)
>>> y.next()
0
>>> y.next()
1
>>> y.next()
2
>>> y.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 14, in next

停止迭代

许多内置函数接受迭代器作为参数。

>>> list(yrange(5))
[0, 1, 2, 3, 4]
>>> sum(yrange(5))
10

在上面的例子中,iterable 和 iterator 都是同一个对象。请注意 iter 方法 returned self.不必总是这样。

class zrange:
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        return zrange_iter(self.n)

class zrange_iter:
    def __init__(self, n):
        self.i = 0
        self.n = n

    def __iter__(self):
        # Iterators are iterables too.
        # Adding this functions to make them so.
        return self

    def next(self):
        if self.i < self.n:
            i = self.i
            self.i += 1
            return i
        else:
            raise StopIteration()

如果可迭代对象和迭代器是同一个对象,则在单次迭代中使用。

>>> y = yrange(5)
>>> list(y)
[0, 1, 2, 3, 4]
>>> list(y)
[]
>>> z = zrange(5)
>>> list(z)
[0, 1, 2, 3, 4]
>>> list(z)
[0, 1, 2, 3, 4]

5.2。发电机

生成器简化了迭代器的创建。生成器是一种生成一系列结果而不是单个值的函数。

def yrange(n):
   i = 0
    while i < n:
        yield i
        i += 1

每次执行 yield 语句时,函数都会生成一个新值。

>>> y = yrange(3)
>>> y
<generator object yrange at 0x401f30>
>>> y.next()
0
>>> y.next()
1
>>> y.next()
2
>>> y.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

停止迭代

所以生成器也是迭代器。您不必担心迭代器协议。

“生成器”一词被混淆地用于表示生成的函数和生成的内容。在本章中,我将使用“生成器”一词来表示生成的对象,使用“生成器函数”来表示生成它的函数。

你能想一想它在内部是如何工作的吗?

当生成器函数被调用时,它 return 是一个生成器对象,甚至没有开始执行该函数。当第一次调用 next 方法时,函数开始执行,直到到达 yield 语句。产生的值由下一次调用return编辑。

以下示例演示了 yield 和调用生成器对象的 next 方法之间的相互作用。

>>> def foo():
...     print "begin"
...     for i in range(3):
...         print "before yield", i
...         yield i
...         print "after yield", i
...     print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0
0
>>> f.next()
after yield 0
before yield 1
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

停止迭代

让我们看一个例子:

def integers():
    """Infinite sequence of integers."""
    i = 1
    while True:
        yield i
        i = i + 1

def squares():
    for i in integers():
        yield i * i

def take(n, seq):
    """Returns first n values from the given sequence."""
    seq = iter(seq)
    result = []
    try:
        for i in range(n):
            result.append(seq.next())
    except StopIteration:
        pass
    return result

print take(5, squares()) # prints [1, 4, 9, 16, 25]