012. 可迭代对象和迭代器有啥区别?

  • 2者不是一回事(废话)

  • 比如

    from collections.abc import Iterable,Iterator
    print(isinstance([1, 2], Iterable))  # True 列表是可迭代对象
    print(isinstance([1, 2], Iterator))  # False 但不是迭代器
    

官方文档

https://docs.python.org/zh-cn/3.10/glossary.html

可迭代对象Iterable

  • 能够逐一返回其成员项的对象。

    简单说,可迭代对象就是能提供迭代器的任意对象
    
  • 可迭代对象的例子包括所有序列类型 (例如 list, strtuple) 以及某些非序列类型例如 dict, 文件对象 以及定义了 __iter__() 方法或是实现了 序列 语义的 __getitem__() 方法的任意自定义类对象。

  • 可迭代对象被可用于 for 循环以及许多其他需要一个序列的地方(zip()map() ...)。

  • 当一个可迭代对象作为参数传给内置函数 iter() 时,它会返回该对象的迭代器。这种迭代器适用于对值集合的一次性遍历。在使用可迭代对象时,你通常不需要调用 iter() 或者自己处理迭代器对象。for 语句会为你自动处理那些操作,创建一个临时的未命名变量用来在循环期间保存迭代器。参见 iteratorsequence 以及 generator


稍作解释

  1. 列表、字符串、元组、字典、集合、文件对象等都是iterable的
from collections.abc import Iterable, Iterator

f = open(r'd:\1.jpg', 'rb')
for _ in ([1, ], (1,), '1', {1: 1}, {1}, f):
    assert isinstance(_, Iterable), f'{_}不是可迭代对象iterable'
else:
    print('都是可迭代对象iterable')
  1. 自定义类

实现_iter_

from collections.abc import Iterable, Iterator

class A:
    def __iter__(self):
        pass  # 实际你肯定不是这样写的

print(isinstance(A(),Iterable))  # True
print(isinstance(A(),Iterator))  # False  注意 , 是Iterable但不是Iterator

实现_getitem_,不符预期

from collections.abc import Iterable, Iterator


class A:
    def __init__(self):
        self.elements = [1, 2, 3]

    def __getitem__(self, i):
        return self.elements[i]


a = A()
print(isinstance(a, Iterable))    # 此处是False的,但下面都是ok的
print(a[0])
for i in a:
    print(i)
# 所以,https://www.liaoxuefeng.com/wiki/1016959663602400/1017323698112640
# 中提到的“凡是可作用于for循环的对象都是Iterable类型”  这句话是不够准确的
# 但多数情况是OK的
  1. 当一个可迭代对象作为参数传给内置函数 iter() 时,它会返回该对象的迭代器
from collections.abc import Iterable, Iterator
list1 = [1,2,3]
it = iter(list1)  # 得到了list对象的迭代器 
print(type(it))  # <class 'list_iterator'>
print(isinstance(it,Iterable))  # True
print(isinstance(it,Iterator))  # True
for _ in it:
    print(_)
s1 = 'abc'
d1 = {1:1}
set1 = {1}
it_s1 = iter(s1)
it_d1 = iter(d1)
it_set1 = iter(set1)
print(type(it_s1))  # str_iterator
print(type(it_d1)) # dict_keyiterator  注意此处,是key的iterator
print(type(it_set1)) # set_iterator

迭代器Iterator

  • 用来表示一连串数据流的对象。
  • 重复调用迭代器的 __next__() 方法(或将其传给内置函数 next())将逐个返回流中的项。当没有数据可用时则将引发 StopIteration 异常。到这时迭代器对象中的数据项已耗尽,继续调用其 __next__() 方法只会再次引发 StopIteration 异常。
  • 迭代器必须具有 __iter__() 方法用来返回该迭代器对象自身,因此迭代器必定也是可迭代对象,可被用于其他可迭代对象适用的大部分场合。一个显著的例外是那些会多次重复访问迭代项的代码。
  • 容器对象(例如 list)在你每次向其传入 iter() 函数或是在 for 循环中使用它时都会产生一个新的迭代器。如果在此情况下你尝试用迭代器则会返回在之前迭代过程中被耗尽的同一迭代器对象,使其看起来就像是一个空容器。
  • 更多信息可查看 迭代器类型
  • CPython 实现细节: CPython 没有统一应用迭代器定义 __iter__() 的要求

稍作解释

  1. 迭代器必定也是可迭代对象

    当前命题的反面
    
  2. 关于next()

    list1 = [1,2,3]
    print(next(list1))  # TypeError: 'list' object is not an iterator
    
    list1 = [1,2,3]
    it_list1 = iter(list1)
    print(next(it_list1)) # 1
    print(next(it_list1)) # 2
    print(next(it_list1)) # 3
    print(next(it_list1)) # StopIteration
    
    list1 = [1,2,3]
    it_list1 = iter(list1)
    print(it_list1.__next__())  # 跟刚才调用next是一样的效果
    # 所以说__next__这种魔术方法的背后往往有一个内置函数(比如len)、运算符(比如>)、操作(比如下标)与之对应
    

其他

  1. for循环的本质:通过iter获取可迭代对象后,不断的在调用next()

    list1 = [1,2,3]
    for _ in list1:
        print(_)
    print('----华丽的分割线-----')
    it_list1 = iter(list1)
    while 1:
        try:
            print(next(it_list1))
        except StopIteration:
            print('到头了')
            break
    
    # for定义
    for_stmt ::=  "for" target_list "in" expression_list ":" suite
                  ["else" ":" suite]
    
    • for 语句用于对序列(例如字符串、元组或列表)或其他可迭代对象中的元素进行迭代:
    • 表达式列表会被求值一次;它应该产生一个可迭代对象。
  2. 为何list不是iterator呢?

    1. 哪里来那么多为什么哦
    2. 你通过代码就可以确定,from collections.abc import Iterable
    3. 非要说,Iterator是惰性的,list是有限的,Iterator可以表示无限数据,比如著名的斐波那契数列

小结

  • 迭代器iterator

    • 迭代器协议是指
    __iter__:返回迭代器对象本身
    __next__:从容器中返回下一项,必须要有它,确保在next()作用下可以得到下一项
    
    • 迭代器iterator一定是可迭代对象
    • iterator一定是有状态的,它要知道我数到哪里了,但却并不需要实现一个容器
  • 可迭代对象iterable

    • 不是迭代器(2者是有区别的!!)

    • 如果一个对象能生成迭代器,那么它就会被称作 iterable

    • for .. in 后面的这个玩意必须是一个iterable(好像跟上面冲突了?其他不然)

    • iterable更像是一个数据的保存者,一个container,它是可以没有状态的,它可以完全不知道你这个iterator数到哪里了,但它需要有能力能产生一个iterator

    • 需要有以下之一

      __iter__:
      __getitem__:需要是个sequence
      
  • 可以有东西既是iterable又是iterator的

    # https://www.bilibili.com/video/BV1ca411t7A9
    # 参考码农高天的示例,稍作更改
    from collections.abc import Iterator, Iterable
    
    
    class Node:
        """
        Iterable
        """
    
        def __init__(self, name):
            self.name = name
            self.next = None
    
        def __iter__(self):
            return NodeIterator(self)
    
    
    class NodeIterator:
    
        def __init__(self, node):
            self.current_node = node
    
        def __next__(self):
            if self.current_node is None:
                raise StopIteration
            node, self.current_node = self.current_node, self.current_node.next
            return node
    
        def __iter__(self):
            return self
    
    
    node1 = Node('node1')
    
    node1_iterator = NodeIterator(node1)
    
    print(isinstance(node1, Iterable))  # T
    print(isinstance(node1_iterator, Iterable)) # T
    print(isinstance(node1, Iterator)) # F
    print(isinstance(node1_iterator, Iterator)) # T
    
    node2 = Node('node2')
    node3 = Node('node3')
    node1.next = node2
    node2.next = node3
    for n in node1:
        print(n.name) # 依次输出node1 node2 node3