Python for循环和迭代器行为

Mat*_*teo 42 python iterator

我想了解更多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]: 
Run Code Online (Sandbox Code Playgroud)

在通过迭代器(In [2])的第一个循环后,它就好像被消耗并留空,所以第二个循环(In [3])不打印任何内容.

但是我从未为iter变量赋予新值.

for循环的引擎下真正发生了什么?

Ric*_*ica 53

你的怀疑是正确的:迭代器已被消耗.

实际上,您的迭代器是一个生成器,它是一个只能迭代一次的对象.

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
Run Code Online (Sandbox Code Playgroud)

他们有效率的原因与告诉你"通过引用"下一步是什么无关.它们很有效,因为它们只根据要求生成下一个项目; 所有项目不会立即生成.事实上,你可以有一个无限的发电机:

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!
Run Code Online (Sandbox Code Playgroud)

其他一些更正有助于提高您的理解:

  • 生成器不是指针,并且不像您在其他语言中熟悉的那样表现.
  • 与其他语言的区别之一:如上所述,生成器的每个结果都是在运行中生成的.在请求之前不会生成下一个结果.
  • 关键字组合for in接受可迭代对象作为其第二个参数.
  • 可迭代对象可以是生成器,如示例中所示,但它也可以是任何其他可迭代对象,例如list,或dict,或str对象(字符串),或提供所需功能的用户定义类型.
  • iter函数应用于对象以获取迭代器(顺便说一下:不要iter像Python那样在Python中用作变量名 - 它是关键字之一).实际上,更确切地说,调用对象的__iter__方法(在大多数情况下,所有iter函数都是这样做的; __iter__是Python所谓的"魔术方法"之一).
  • 如果调用__iter__成功,则该函数next()在循环中一遍又一遍地应用于可迭代对象,并且将提供给的第一个变量for in分配给next()函数的结果.(请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象.)实际上,更确切地说:它调用迭代器对象的__next__方法,这是另一种"魔术方法".
  • for当循环结束next()引发StopIteration异常(当可迭代不具有另一个目的是产生时通常发生next()被调用).

你可以for用这种方式"手动"实现python循环(可能不完美,但足够接近):

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
Run Code Online (Sandbox Code Playgroud)

上面和你的示例代码之间几乎没有区别.

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

  • 当单独使用时,in关键字首先调用对象的__contains__方法,这是另一种"魔术方法"(请注意,使用时会跳过此步骤for in).使用in一个容器本身,你可以做这样的事情:

    1 in [1, 2, 3] # True
    'He' in 'Hello' # True
    3 in range(10) # True
    'eH' in 'Hello'[::-1] # True
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果可迭代对象不是容器(即它没有__contains__方法),则in接下来尝试调用对象的__iter__方法.如前所述:该__iter__方法返回Python中已知的迭代器.基本上,迭代器是一个对象,您可以next()1上使用内置泛型函数.生成器只是一种迭代器.

  • 如果调用__iter__成功,则in关键字将函数next()一次又一次地应用于可迭代对象.(记住:可迭代对象可以是生成器,容器对象的迭代器,或任何其他可迭代对象.)实际上,更确切地说:它调用迭代器对象的__next__方法).
  • 如果对象没有__iter__返回迭代器的方法,in则使用对象的__getitem__方法2回退旧式迭代协议.
  • 如果上述所有尝试都失败,您将获得TypeError异常.

如果您希望创建自己的对象类型进行迭代(即,您可以使用它for in,或者只是in对它进行迭代),了解yield关键字(在生成器中使用)(如上所述)非常有用.

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

m = MyIterable()
for _ in m: print(_) # 1
1 in m # True    
Run Code Online (Sandbox Code Playgroud)

yield函数或方法转换为生成器而不是常规函数/方法的存在.__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
Run Code Online (Sandbox Code Playgroud)

1请注意,要成为迭代器,对象必须实现迭代器协议.这只意味着必须正确实现__next____iter__方法(生成器"免费"提供此功能,因此您在使用它时无需担心).另请注意,该方法实际上(没有下划线)在Python 2中.___next__next

2有关 创建可迭代类的不同方法,请参阅此答案.

  • 这个答案将`in`运算符与在[1,2,3]中的`1中使用`和`for`循环中的关键字用法混淆.`in`运算符只调用[`__contains__`方法](https://docs.python.org/2.7/reference/datamodel.html#object.__contains__),如果方法没有,则回退到迭代对象存在. (4认同)
  • 你的"手动"循环是可疑的.你只需要分配给`iterable .__ next__`(可能存在或者可能不存在于迭代中!)并且永远不会调用它 (2认同)

Mar*_*cin 18

For循环基本上调用next应用于(__next__在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 
Run Code Online (Sandbox Code Playgroud)

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

print(next(iter))  
Run Code Online (Sandbox Code Playgroud)

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


小智 6

python中有一个迭代器协议,它定义了for语句对列表和dicts的行为,以及其他可以循环的东西.

它在这里这里的python文档中.

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

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

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

for i in my_iter():
    print i
Run Code Online (Sandbox Code Playgroud)

结果是:

1
2
3
Run Code Online (Sandbox Code Playgroud)

关于这一点需要注意几点.my_iter是一个函数.my_iter()返回一个迭代器.

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

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
Run Code Online (Sandbox Code Playgroud)

结果与上述相同.当我们进入第二个for循环时,iter就会耗尽.

但对于更复杂的事情,这是相当简单的呢?也许在一个循环中为什么不呢?

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

for y in capital_iter('bobert'):
    print y
Run Code Online (Sandbox Code Playgroud)

当它运行时,我们在字符串类型(内置于iter中)使用迭代器.反过来,这允许我们对它运行for循环,并产生结果,直到我们完成.

B
O
B
E
R
T
Run Code Online (Sandbox Code Playgroud)

所以现在这引出了一个问题,那么迭代器中的收益之间会发生什么呢?

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
Run Code Online (Sandbox Code Playgroud)

答案是函数在等待下一次调用next()的yield时暂停.

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