Python for循环和迭代器行为

我想了解更多有关for的信息,所以如果我错了,请纠正我。

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

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

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

在遍历迭代器(for)的第一个循环之后,就好像它已被消耗并留空,因此第二个循环(In [3])不打印任何内容。

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

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

Matteo asked 2020-08-06T10:04:17Z
6个解决方案
53 votes

您的怀疑是正确的:迭代器已被消耗。

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

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

它们高效的原因与“参考”告诉您下一步是什么无关。 它们之所以有效,是因为它们仅根据请求生成下一个项目。 所有项目都不是一次生成的。 实际上,您可以拥有一个无限生成器:

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!

其他一些更正可以帮助您增进理解:

  • 生成器不是指针,并且不像您在其他语言中可能熟悉的那样表现为指针。
  • 与其他语言的区别之一:如上所述,生成器的每个结果都是即时生成的。 除非请求,否则不会产生下一个结果。
  • 关键字组合next __iter__接受可迭代对象作为其第二个参数。
  • 在您的示例情况下,可迭代对象可以是生成器,但也可以是任何其他可迭代对象,例如next__iter_____next__对象(字符串),或提供所需内容的用户定义类型 功能。
  • next函数应用于对象以获取迭代器(顺便说一句:不要像在Python中那样使用next作为变量名,它是关键字之一)。 实际上,更确切地说,对象的next方法被调用(在大多数情况下,所有__iter__函数都可以执行; ___next__是Python所谓的“魔术方法”之一)。
  • 如果对next的调用成功,则将函数next循环循环应用于可迭代对象,并将提供给__iter__ ___next__的第一个变量分配给__contains__函数的结果。 (请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更准确地说:它调用了迭代器对象的next方法,这是另一种“魔术方法”。
  • __iter__引发next异常时(通常发生在调用2999272270536901901的可迭代对象没有其他对象产生时,通常会发生),next循环结束。

您可以通过这种方式“手动”在python中实现next循环(可能并不完美,但足够接近):

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

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

实际上,next循环中最有趣的部分不是__iter__,而是___next__。单独使用__contains__会产生与for in不同的效果,但是了解forfor的处理方式非常有用,因为for 类似的行为。

  • 单独使用时,next关键字首先调用对象的next方法,这是另一种“魔术方法”(请注意,使用__iter__ ___next__时会跳过此步骤)。 在容器上单独使用__contains__,您可以执行以下操作:

    next
  • 如果可迭代对象不是容器(即它没有next方法),则__iter__接下来将尝试调用该对象的___next__方法。 如前所述:__contains__方法返回Python中称为迭代器的内容。 基本上,迭代器是一个对象,您可以使用内置的泛型函数next on1。 生成器只是迭代器的一种类型。

  • 如果对next的调用成功,则__iter__关键字将函数next一次又一次地应用于可迭代对象。 (请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更确切地说,它调用迭代器对象的next方法。
  • 如果对象不具有返回迭代器的next方法,则__iter__然后使用对象的___next__方法2返回旧式迭代协议。
  • 如果以上所有尝试均失败,您将收到next异常。

如果您希望创建自己的对象类型以进行迭代(例如,可以在其上使用next __iter__或仅使用___next__),了解__contains__关键字(在生成器中使用)就很有用(如上所述)。

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

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

next的存在将功能或方法转换为生成器,而不是常规功能/方法。 如果使用生成器,则不需要__iter__方法(生成器会自动将___next__随其一起提供)。

如果要创建自己的容器对象类型(即可以单独使用next,但不能使用__iter__ ___next__),则只需要__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注意,要成为迭代器,对象必须实现迭代器协议。 这仅意味着必须正确实现next__iter__方法(发电机“免费”提供了此功能,因此您在使用它们时无需担心)。 另请注意,Python 2中的___next__方法实际上是next(无下划线)。

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

Rick Teachey answered 2020-08-06T10:06:11Z
18 votes

For循环基本上调用了应用到的对象的next方法(Python 3中为StopIteration)。

您可以通过执行以下操作来简单模拟:

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将停止。 并且迭代器可以是任何将响应next()函数并在没有更多元素时引发异常的对象。 它不必是任何指针或引用(在C / C ++的意义上,python中也没有这样的东西),链接列表等。

Marcin answered 2020-08-06T10:06:44Z
6 votes

python中有一个迭代器协议,该协议定义yield语句如何使用列表和字典以及其他可以循环的行为。

在此处和此处的python文档中。

迭代器协议的工作方式通常采用python生成器的形式。 只要我们有一个值,直到结束,然后我们加注StopIteration,我们yield就会有一个值

因此,让我们编写自己的迭代器:

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()返回一个迭代器。

如果我是这样写的,则使用迭代器:

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循环时,迭代器已耗尽。

但这很简单,那么复杂的事情呢? 也许也许是一个循环,为什么不呢?

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

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

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

B
O
B
E
R
T

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

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

答案是该函数暂停在收益率上,等待下一次对next()的调用。

B
O
B
Hey There!
E
R
T
Traceback (most recent call last):
  File "", line 13, in 
    StopIteration
MadMan2064 answered 2020-08-06T10:07:53Z
4 votes

有关__iter____getitem__类缺少自己的__getitem__方法的行为的其他详细信息。


__iter__之前有__getitem__。如果__getitem__0-len(obj)-1中的ints一起使用,则iter()支持这些对象。 它将构造一个新的迭代器,该迭代器将重复调用__getitem__012...,直到获得IndexError,然后将其转换为IndexError

有关创建迭代器的不同方法的更多详细信息,请参见此答案。

Ethan Furman answered 2020-08-06T10:08:23Z
2 votes

概念1

所有生成器都是迭代器,但不是所有迭代器

概念2

迭代器是具有下一个(Python 2)或下一个(Python 3)的对象 方法。

概念3

从Wiki报价 发电机发电机 函数可让您声明行为类似于 迭代器,即可以在for循环中使用。

就你而言

>>> it = (i for i in range(5))
>>> type(it)
<type 'generator'>
>>> callable(getattr(it, 'iter', None))
False
>>> callable(getattr(it, 'next', None))
True
Abhijit answered 2020-08-06T10:09:09Z
2 votes

摘自《 Python实践》一书:


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接受一个可迭代的对象并返回一个迭代器。

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

StopIteration

每次我们在迭代器上调用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的返回值是一个迭代器。 它应该有一个下一个方法,并在没有更多元素时引发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

StopIteration

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

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

在上述情况下,Iterable和Iterator都是同一个对象。 注意iter方法返回了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()

如果iterable和iterator都是同一对象,则在单个迭代中使用它。

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

StopIteration

因此,生成器也是迭代器。 您不必担心迭代器协议。

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

您能考虑一下它的内部运作方式吗?

调用生成器函数时,它甚至不开始执行函数就返回生成器对象。 首次调用next方法时,函数开始执行直到到达yield语句。 产生的值由下一个调用返回。

下面的示例演示了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>

StopIteration

让我们来看一个例子:

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]
drewteriyaki answered 2020-08-06T10:11:46Z
translate from https://stackoverflow.com:/questions/29403401/python-for-loop-and-iterator-behavior