迭代器(iterator)、可迭代对象(iterable)和迭代(iteration)是什么?

563
在Python中,“iterable”、“iterator”和“iteration”是什么意思?它们是如何定义的?
参见:如何构建基本迭代器?

希望能对你有所帮助:https://towardsdatascience.com/python-iterables-vs-iterators-688907fd755f - Giorgos Myrianthous
16个回答

647

迭代是一个通用术语,指的是逐个处理某个东西中的每个项目。只要您使用循环(显式或隐式)遍历一组项目,就属于迭代。

在Python中,可迭代对象迭代器有特定的含义。

可迭代对象是具有__iter__方法的对象,该方法返回一个迭代器,或者定义了一个__getitem__方法,该方法可以从零开始连续索引(并在索引不再有效时引发IndexError)。 因此,可迭代对象是您可以从中获取迭代器的对象。

迭代器是具有next(Python 2)或__next__(Python 3)方法的对象。

每当您在Python中使用for循环,map或列表推导等功能时,都会自动调用next方法来从迭代器获取每个项目,从而完成迭代的过程。

学习的好起点是教程中的迭代器部分和标准类型页面的迭代器类型部分。掌握基础知识后,可以尝试阅读函数式编程 HOWTO 中的 迭代器部分


3
请注意,collections.abc.AsyncIterator 检查是否存在 __aiter____anext__ 方法。这是 Python 3.6 中的新功能。 - Janus Troelsen
2
@jlh 为什么 __len__ 必须与迭代相关联?知道某物的长度如何帮助您对其进行迭代? - shadowtalker
3
了解哪些索引是有效的可以帮助您知道哪些索引可以与 __getitem__ 一起使用。 - jlh
5
@jlh,听起来你在提出一个非常具有个人意见的默认行为。请考虑到 {'a': 'hi', 'b': 'bye'} 的长度为2,但不能通过0、1或2进行索引。 - shadowtalker
3
@shadowtalker。但是字典有一个__iter__方法。我认为jlh指的是特定可迭代对象,因为它们定义了:“一个__getitem__方法,可以从零开始使用顺序索引”。 - Rich
显示剩余6条评论

407
这是我在教授Python课程中使用的解释:
可迭代对象(ITERABLE)是:
- 任何可以循环的东西(例如,您可以循环遍历字符串或文件), - 任何可以出现在for循环的右侧的东西:for x in iterable: ... 或者 - 任何您可以使用iter()调用以返回迭代器的东西:iter(obj) 或者 - 定义了__iter__方法以返回新迭代器的对象,或者它可能具有适用于索引查找的__getitem__方法。
迭代器(ITERATOR)是一个对象:
- 具有状态,记住其在迭代期间的位置, - 具有一个__next__方法,该方法:
- 返回迭代中的下一个值 - 更新状态以指向下一个值 - 通过引发StopIteration信号表示完成
- 是可自迭代的(即它具有一个__iter__方法,该方法返回self)。
注:
- 在Python 3中,__next__方法的拼写方式为next,而在Python 2中则不同。 - 内置函数next()会调用传递给它的对象上的__next__方法。
例如:
>>> s = 'cat'      # s is an ITERABLE
                   # s is a str object that is immutable
                   # s has no state
                   # s has a __getitem__() method 

>>> t = iter(s)    # t is an ITERATOR
                   # t has state (it starts by pointing at the "c"
                   # t has a next() method and an __iter__() method

>>> next(t)        # the next() function returns the next value and advances the state
'c'
>>> next(t)        # the next() function returns the next value and advances
'a'
>>> next(t)        # the next() function returns the next value and advances
't'
>>> next(t)        # next() raises StopIteration to signal that iteration is complete
Traceback (most recent call last):
...
StopIteration

>>> iter(t) is t   # the iterator is self-iterable

4
“fresh iterator”的意思是新的迭代器。 - lmiguelvargasf
24
“Fresh”指的是“新的和未被使用的”,与“用尽或部分使用”的概念相对。这意味着新的迭代器从开头开始,而部分使用的迭代器则从离开的地方继续。 - Raymond Hettinger
1
你的第二、第三和第四个要点清楚地表明了你的意思,涉及到具体的Python构造、内置函数或方法调用。但是第一个要点(“任何可以循环遍历的东西”)缺乏这种明确性。此外,第一个要点似乎与第二个要点重叠,因为第二个要点涉及for循环,而第一个要点涉及“循环遍历”。请问您能否解决这些问题? - fountainhead
9
请考虑将“anything your can call with iter()”改为“anything you can pass to iter()”,使句子更通俗易懂,但不改变原意。 - fountainhead
3
如果只有__getitem__()方法而没有__iter__()方法,那么可迭代对象的一个例子是什么? - Niko Pasanen

124
上面的回答很好,但是对于像我这样的人,没有强调“区别”足够重要。此外,人们倾向于过分使用Pythonic将定义放在前面,例如“X是具有__foo __()方法的对象”。这些定义是正确的-它们基于鸭子类型哲学,但是关注方法倾向于在试图理解概念时产生混淆。
因此,我添加了我的版本。
自然语言中,“迭代”是一次在元素行中取一个元素的过程。
在Python中,“可迭代”是一个可以迭代的对象,简单地说,就是它可以在迭代中使用,例如使用for循环。通过使用“迭代器”来实现。我将在下面解释。
而“迭代器”是定义如何进行迭代的对象-具体来说是下一个元素是什么。这就是为什么它必须具有next()方法。
迭代器本身也是可迭代的,其区别在于它们的__iter __()方法返回相同的对象(self),无论先前调用next()是否已消耗其项。
那么当Python解释器看到“for x in obj:”语句时,它会想什么?
看,一个for循环。看起来是迭代器的工作...让我们找一个。...有这个obj人,所以让我们问他。
“obj先生,你有迭代器吗?”(调用iter(obj),它调用obj.__iter__(),它愉快地交出一个闪亮的新迭代器_i。)
好的,那很容易...让我们开始迭代。 (x = _i.next() ... x = _i.next()...)
由于obj先生通过了这个测试(通过具有返回有效迭代器的某些方法),我们奖励他形容词:现在您可以称呼他为“可迭代的obj先生”。
但是,在简单情况下,通常不会从分别拥有迭代器和可迭代对象中受益。因此,您只定义一个对象,它也是自己的迭代器。(Python并不真正关心由obj提供的_i并不是那么闪亮,而只是obj本身。)
这就是为什么在我看到的大多数例子中(也是一遍又一遍困扰我的原因),你会看到:
class IterableExample(object):

    def __iter__(self):
        return self

    def next(self):
        pass

替代

class Iterator(object):
    def next(self):
        pass

class Iterable(object):
    def __iter__(self):
        return Iterator()

有些情况下,将迭代器与可迭代对象分开会更有益,比如想要一行项目,但有更多的“光标”时。例如,在处理“当前”和“即将到来”的元素时,可以分别使用不同的迭代器。或者多个线程从一个巨大的列表中读取数据:每个线程都可以有自己的迭代器来遍历所有条目。@Raymond@glglgl 在上面的答案中提供了更多信息。

想象一下你能做什么:

class SmartIterableExample(object):

    def create_iterator(self):
        # An amazingly powerful yet simple way to create arbitrary
        # iterator, utilizing object state (or not, if you are fan
        # of functional), magic and nuclear waste--no kittens hurt.
        pass    # don't forget to add the next() method

    def __iter__(self):
        return self.create_iterator()

注释:

  • 我再次重申:迭代器不可迭代。 迭代器不能作为for循环中的“源”。for循环主要需要的是__iter__()(返回具有next()的内容)。

  • 当然,for并不是唯一的迭代循环,因此上述规则也适用于其他结构(如while…)。

  • 迭代器的next()可以抛出StopIteration以停止迭代。 但不必这样做,它可以永远迭代或使用其他方法。

  • 在上述“思考过程”中,_i实际上不存在。 我编了这个名字。

  • Python 3.x中有一个小变化:next()方法(而不是内置方法)现在必须称为__next__()。 是的,一直都应该是这样。

  • 你也可以这样想:可迭代对象具有数据,迭代器提取下一个项

免责声明:我不是任何Python解释器的开发人员,因此我不真正知道解释器的“想法”。 上述思考仅是展示我如何从其他解释、实验和Python新手的实际经验中理解该主题。


3
很好,但我还是有点困惑。我原以为你的黄色框是在说for循环需要一个迭代器(“看,一个for循环。看起来需要一个迭代器……赶紧拿一个。”)。但是在结尾的注释中你又说,“迭代器不能用作for循环中的源”……? Translated: 很好,但我还是有点困惑。我原以为你的黄色框是在说for循环需要一个迭代器(“看,一个for循环。看起来需要一个迭代器……赶紧拿一个。”)。但是在结尾的注释中你又说,“迭代器不能用作for循环中的源”……? - Racing Tadpole
1
为什么你在这些'next'的定义中只注明了 pass?我猜你的意思是要求有人实现获取下一个的方法,因为'next'必须返回一些东西。 - nealmcb
1
@nealmcb 是的,我认为那就是过去的我所想的。(毕竟这就是 pass 的用处) - Alois Mahdal
2
@AloisMahdal 啊,我之前没见过那种用法。当我看到 pass 时,我认为它是为了语法而存在的。我刚刚在 省略号对象 上找到了一些有趣的答案:你可以使用 ... 来表示一个“稍后完成”的块。NotImplemented 也是可用的。 - nealmcb
5
虽然我喜欢你强调迭代器和可迭代对象之间的区别,但是这个答案自相矛盾。首先你写道,“迭代器本身也是可迭代的”(与 Python 文档 中所写的相符)。但后来你又写道:“迭代器不可迭代。迭代器不能在 for 循环中用作“源”。我理解你回答的重点并且除此之外也很喜欢它,但我认为修复这个问题会更好一些。 - Rich

30

可迭代对象是一个具有__iter__()方法的对象。它可以被多次迭代,例如list()tuple()

迭代器是迭代的对象。它由__iter__()方法返回,通过自己的__iter__()方法返回它本身,并具有一个next()方法(在3.x中为__next__())。

迭代是调用next()__next__()直到它引发StopIteration的过程。

示例:

>>> a = [1, 2, 3] # iterable
>>> b1 = iter(a) # iterator 1
>>> b2 = iter(a) # iterator 2, independent of b1
>>> next(b1)
1
>>> next(b1)
2
>>> next(b2) # start over, as it is the first call to b2
1
>>> next(b1)
3
>>> next(b1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> b1 = iter(a) # new one, start over
>>> next(b1)
1

1
那么,它只是一个通过容器的对象?这在哪些方面有用呢? - thechrishaddad
2
通常情况下,生成器、文件或数据库游标只能被迭代一次,因此它们是自己的迭代器。 - glglgl
1
我猜b2不必独立于b1?对于这种特殊情况,它是独立的,当然我可以使它不独立,但也是一个有效的“Iterable”。 - Bin
2
@PatrickT 三个都可以:是的。试一下吧。for i in [1,3,4,6]: print(i) / for i in {1,3,4,6}: print(i) / for i in (1,3,4,6): print(i)。此外,查看文档或语言规范。 - glglgl
2
@PatrickT 这可能取决于Python版本和执行历史记录(例如对象ID /地址,它们的类型等)。如果您需要有序集合,请参阅关于有序集合的更多信息此问题 - glglgl
显示剩余3条评论

21

这是我的备忘单:

 sequence
  +
  |
  v
   def __getitem__(self, index: int):
  +    ...
  |    raise IndexError
  |
  |
  |              def __iter__(self):
  |             +     ...
  |             |     return <iterator>
  |             |
  |             |
  +--> or <-----+        def __next__(self):
       +        |       +    ...
       |        |       |    raise StopIteration
       v        |       |
    iterable    |       |
           +    |       |
           |    |       v
           |    +----> and +-------> iterator
           |                               ^
           v                               |
   iter(<iterable>) +----------------------+
                                           |
   def generator():                        |
  +    yield 1                             |
  |                 generator_expression +-+
  |                                        |
  +-> generator() +-> generator_iterator +-+

测验:

  1. 每个迭代器都是可迭代对象吗?
  2. 容器对象的 __iter__()方法可以实现为生成器吗?
  3. 具有 __next__ 方法的可迭代对象不一定是迭代器吗?

答案:

  1. 每个迭代器都必须具有 __iter__ 方法。拥有 __iter__ 就足以成为可迭代对象。 因此,每个迭代器都是可迭代对象。
  2. 调用 __iter__ 时,它应该返回一个迭代器(在上面的图表中返回 <iterator>)。调用生成器会返回一个生成器迭代器,这是一种迭代器类型。

  3. 具有 __next__ 方法的可迭代对象不一定是迭代器。要成为迭代器,还必须具有 __iter__ 方法。
class Iterable1:
    def __iter__(self):
        # a method (which is a function defined inside a class body)
        # calling iter() converts iterable (tuple) to iterator
        return iter((1,2,3))

class Iterable2:
    def __iter__(self):
        # a generator
        for i in (1, 2, 3):
            yield i

class Iterable3:
    def __iter__(self):
        # with PEP 380 syntax
        yield from (1, 2, 3)

# passes
assert list(Iterable1()) == list(Iterable2()) == list(Iterable3()) == [1, 2, 3]
以下是一个示例:
  • class MyIterable:
    
        def __init__(self):
            self.n = 0
    
        def __getitem__(self, index: int):
            return (1, 2, 3)[index]
    
        def __next__(self):
            n = self.n = self.n + 1
            if n > 3:
                raise StopIteration
            return n
    
    # if you can iter it without raising a TypeError, then it's an iterable.
    iter(MyIterable())
    
    # but obviously `MyIterable()` is not an iterator since it does not have
    # an `__iter__` method.
    from collections.abc import Iterator
    assert isinstance(MyIterable(), Iterator)  # AssertionError
    

  • 2
    在这个小测验中,我只理解了第一个要点, 即迭代器因为有 __iter__ 方法而成为可迭代对象。您可以通过编辑此答案来详细解释第二个和第三个要点吗? - AnV
    1
    据我所知:关于第二点:__iter __()返回一个迭代器。生成器是一种迭代器,因此可以用于这个目的。关于第三点:我只能猜测,但我认为如果缺少__iter __()或者它没有返回self,那么它就不是一个迭代器,因为迭代器的__iter __()必须返回self - glglgl
    2
    有趣的事情是 isinstance(MyIterable(), collections.abc.Iterable) 也是 False。@_@ - upgrd

    11

    我不知道是否有帮助,但我总是喜欢在脑海中形象化概念以更好地理解它们。因此,由于我有一个小儿子,我用砖块和白纸来形象化可迭代/迭代器概念。

    假设我们在黑暗的房间里,地上有砖块供我儿子玩耍。这些砖块大小、颜色各异,现在不重要。假设我们有5个这样的砖块。可以将这5个砖块描述为一个对象——比如说砖块套件。我们可以做很多事情——可以拿第一个然后拿第二个再拿第三个,可以改变砖块的位置,把第一个砖块放在第二个砖块上面等等。我们可以做许多种类的事情。因此,这个砖块套件是一个可迭代对象序列,因为我们可以遍历每个砖块并对其进行操作。我们只能像我小儿子那样玩弄一个砖块一次。所以我再次想象这个砖块套件是一个可迭代对象

    现在记住我们在黑暗的房间里。或者几乎黑暗。问题是我们并看不清这些砖块,它们是什么颜色,什么形状等等。因此,即使我们想对它们进行操作——即遍历它们——我们也不知道该怎么做,因为太黑了。

    我们可以在第一个砖块旁边——作为砖块套件的元素——放一张白色荧光纸,以便我们看到第一个砖块元素的位置。每次取出砖块时,我们将白色荧光纸替换为下一个砖块,以便在黑暗房间中能够看到它。这张白纸不过是一个迭代器。它也是一个对象。但是,它是一个我们可以使用并玩弄可迭代对象——砖块套件——元素的对象。

    顺便说一下,这解释了我的早期错误,当我在IDLE中尝试以下操作时会收到TypeError:

     >>> X = [1,2,3,4,5]
     >>> next(X)
     Traceback (most recent call last):
        File "<pyshell#19>", line 1, in <module>
          next(X)
     TypeError: 'list' object is not an iterator
    

    这里的List X 是我们的积木套件,而不是一张白纸。我需要先找到一个迭代器:

    >>> X = [1,2,3,4,5]
    >>> bricks_kit = [1,2,3,4,5]
    >>> white_piece_of_paper = iter(bricks_kit)
    >>> next(white_piece_of_paper)
    1
    >>> next(white_piece_of_paper)
    2
    >>>
    

    不知道这是否有帮助,但它对我有帮助。如果有人可以确认/纠正概念的可视化,我将不胜感激。这将帮助我学到更多。


    7

    可迭代对象具有__iter__方法,每次实例化一个新的迭代器。

    迭代器实现了__next__方法,返回单个项,并实现了__iter__方法,返回self

    因此,迭代器也是可迭代的,但可迭代对象不是迭代器。

    卢西亚诺·拉玛略,《流畅的Python》。


    7

    我认为你可能不会比文档更简单,但我会尝试:

    • Iterable是可以进行迭代的东西。在实践中,这通常意味着一个序列,例如具有开始和结束以及遍历其中所有项的某种方法。
    • 您可以将Iterator视为帮助器伪方法(或伪属性),它提供(或保持)可迭代中的下一个(或第一个)项。(实际上,它只是定义了next()方法的对象)

    • Iteration可能最好通过Merriam-Webster单词的定义来解释

    b : 重复计算机指令序列一定次数或满足条件 — 比较递归


    6

    可迭代对象:可以被迭代的东西是可迭代的,例如像列表、字符串等序列。此外,它具有__getitem__方法或__iter__方法。现在,如果我们在该对象上使用iter()函数,我们将获得一个迭代器。

    迭代器:当我们从iter()函数获取迭代器对象时,我们调用__next__()方法(在Python3中)或简单地使用next()(在Python2中)逐个获取元素。这个类或这个类的实例被称为迭代器。

    来自文档:

    迭代器在Python中无处不在且统一。在幕后,for语句在容器对象上调用iter()。该函数返回一个迭代器对象,该对象定义了方法__next__(),该方法逐个访问容器中的元素。当没有更多元素时,__next__()会引发一个StopIteration异常,告诉for循环终止。您可以使用内置函数next()调用__next__()方法;以下示例显示了所有内容的工作原理:

    >>> s = 'abc'
    >>> it = iter(s)
    >>> it
    <iterator object at 0x00A1DB50>
    >>> next(it)
    'a'
    >>> next(it)
    'b'
    >>> next(it)
    'c'
    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        next(it)
    StopIteration
    

    一个类的例子:

    class Reverse:
        """Iterator for looping over a sequence backwards."""
        def __init__(self, data):
            self.data = data
            self.index = len(data)
        def __iter__(self):
            return self
        def __next__(self):
            if self.index == 0:
                raise StopIteration
            self.index = self.index - 1
            return self.data[self.index]
    
    
    >>> rev = Reverse('spam')
    >>> iter(rev)
    <__main__.Reverse object at 0x00A1DB50>
    >>> for char in rev:
    ...     print(char)
    ...
    m
    a
    p
    s
    

    5

    迭代器是实现 iternext 方法的对象。如果这些方法被定义,我们可以使用 for 循环或理解。

    class Squares:
        def __init__(self, length):
            self.length = length
            self.i = 0
            
        def __iter__(self):
            print('calling __iter__') # this will be called first and only once
            return self
        
        def __next__(self): 
            print('calling __next__') # this will be called for each iteration
            if self.i >= self.length:
                raise StopIteration
            else:
                result = self.i ** 2
                self.i += 1
                return result
    

    迭代器会被用尽,这意味着在你遍历完所有项目后,就不能再次遍历了,必须重新创建一个新的对象。假设你有一个类,它保存了城市的属性,并且你想要进行迭代遍历。

    class Cities:
        def __init__(self):
            self._cities = ['Brooklyn', 'Manhattan', 'Prag', 'Madrid', 'London']
            self._index = 0
        
        def __iter__(self):
            return self
        
        def __next__(self):
            if self._index >= len(self._cities):
                raise StopIteration
            else:
                item = self._cities[self._index]
                self._index += 1
                return item
    

    类Cities的实例是一个迭代器。如果您想要重新迭代城市,则必须创建一个新对象,这是一项昂贵的操作。可以将该类分为两个类:一个返回城市,另一个返回迭代器,该迭代器使用城市作为init参数。

    class Cities:
        def __init__(self):
            self._cities = ['New York', 'Newark', 'Istanbul', 'London']        
        def __len__(self):
            return len(self._cities)
    
    
    
    class CityIterator:
        def __init__(self, city_obj):
            # cities is an instance of Cities
            self._city_obj = city_obj
            self._index = 0
            
        def __iter__(self):
            return self
        
        def __next__(self):
            if self._index >= len(self._city_obj):
                raise StopIteration
            else:
                item = self._city_obj._cities[self._index]
                self._index += 1
                return item
    

    现在,如果我们需要创建一个新的迭代器,我们不必再创建数据,也就是城市。我们创建城市对象并将其传递给迭代器。但我们仍然在做额外的工作。我们可以通过只创建一个类来实现这一点。 Iterable 是一个Python对象,它实现了 可迭代协议。它只需要 __iter__() 方法,返回一个新的迭代器对象实例即可。
    class Cities:
        def __init__(self):
            self._cities = ['New York', 'Newark', 'Istanbul', 'Paris']
            
        def __len__(self):
            return len(self._cities)
        
        def __iter__(self):
            return self.CityIterator(self)
        
        class CityIterator:
            def __init__(self, city_obj):
                self._city_obj = city_obj
                self._index = 0
    
            def __iter__(self):
                return self
    
            def __next__(self):
                if self._index >= len(self._city_obj):
                    raise StopIteration
                else:
                    item = self._city_obj._cities[self._index]
                    self._index += 1
                    return item
    

    迭代器有__iter____next__,可迭代对象只有__iter__,因此我们可以说迭代器也是可迭代对象,但它们是会用尽的可迭代对象。另一方面,可迭代对象永远不会用尽,因为它们总是返回一个新的迭代器来进行迭代。

    您会注意到,可迭代对象的主要部分在于迭代器中,而可迭代对象本身只是一个额外的层次结构,使我们能够创建和访问迭代器。

    遍历可迭代对象

    Python有一个内置函数iter(),它调用__iter__()。当我们遍历可迭代对象时,Python调用iter(),它返回一个迭代器,然后开始使用迭代器的__next__()来遍历数据。

    请注意,在上面的示例中,Cities创建了一个可迭代对象,但它不是序列类型,这意味着我们无法通过索引获取城市。要解决这个问题,我们应该只需将__get_item__添加到Cities类中即可。

    class Cities:
        def __init__(self):
            self._cities = ['New York', 'Newark', 'Budapest', 'Newcastle']
            
        def __len__(self):
            return len(self._cities)
        
        def __getitem__(self, s): # now a sequence type
            return self._cities[s]
        
        def __iter__(self):
            return self.CityIterator(self)
        
        class CityIterator:
            def __init__(self, city_obj):
                self._city_obj = city_obj
                self._index = 0
    
            def __iter__(self):
                return self
    
            def __next__(self):
                if self._index >= len(self._city_obj):
                    raise StopIteration
                else:
                    item = self._city_obj._cities[self._index]
                    self._index += 1
                    return item
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接