Python iterable(可迭代)和iterator(迭代器)底层源码剖析,简称【迭代器圣经】

2023-12-13 07:36:19

迭代器和和迭代类型

1. 概念明确

1.1 iterable 可迭代的

# 源码在_collections_abc.py
class Iterable(metaclass=ABCMeta):
    @abstractmethod
    def __iter__(self): ...
  • Iterable类型是必须要实现__iter__方法

1.2 iterator 迭代器

# 源码在_collections_abc.py
class Iterator(Iterable):
    @abstractmethod
    def __next__(self): 
        raise StopIteration
    def __iter__(self): 
        return self
  • iterator类型是必须要同时实现__iter__方法和__next__方法
  • 值得一提的是Iterator是继承自Iterable的

2. 如何查看对象是iterable还是iterator

2.1 通过代码判断

from collections.abc import Iterable, Iterator

my_obj = [1, 2, 3]
print(isinstance(my_obj, Iterable))  # True
print(isinstance(my_obj, Iterator))  # False

2.2 通过是否实现相应方法

  • Iterable类型是必须要实现__iter__方法
  • iterator类型是必须要同时实现__iter__方法和__next__方法

3. iter()方法的两个作用

  • iter(iterable) -> iterator:将可迭代对象转成迭代器
from collections.abc import Iterable, Iterator

my_obj = iter([1, 2, 3])
print(isinstance(my_obj, Iterable))  # True
print(isinstance(my_obj, Iterator))  # True
  • iter(callable, sentinel) -> iterator:将可执行的对象,按照停止的条件,生成迭代器对象
# 抛骰子,当抛到5的时候程序停止
import random
from collections.abc import Iterable, Iterator

def fun():
    return random.randint(1, 6)

my_obj = iter(fun, 5)
for i in my_obj:
    print(i)
# 这里是随机的结果哦
# 3
# 4
# 4
# 6
# 1
# 1

print(isinstance(my_obj, Iterable))  # True
print(isinstance(my_obj, Iterator))  # True

4. for ... in ... 到底做了什么?

4.1 for in 必须是iterator类型

  • for ... in ...其实是for ... in <iterator>
    • in后面的对象 必须是iterator迭代器
    • Python解释器会帮我们自动对in后面的对象进行iter(iterable)操作),这也就是解释了为什么我们遍历可迭代对象也是可以的
# for ... in ... 到底干了什么事?
import dis

def func():
    for i in 'abcd':
        pass
dis.dis(func)
"""输出结果如下
  5           0 LOAD_CONST               1 ('abcd')
              2 GET_ITER
        >>    4 FOR_ITER                 4 (to 10)
              6 STORE_FAST               0 (i)

  6           8 JUMP_ABSOLUTE            4
        >>   10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
"""
# 首先LOAD_CONST加载了一个字符串变量
# 然后执行GET_ITER,也就是说会对for in后面的对象自动执行 `iter("abcd")`操作,目的就是将可迭代的数据类型转成迭代器,参见官方文档:
# https://docs.python.org/zh-cn/3/library/dis.html#opcode-GET_ITER

4.2 为什么for in 必须是iterator类型?

  • 因为for ... in ...其实就是每次循环都自动调用next()方法
# iterable可迭代数据类型无法调用next()方法,因为没有实现__next__方法
# 换句话说list类型不是迭代器所以没有实现__next__方法,也就无法使用next()函数进行调用
lt = [1,2,3]
next(lt) # TypeError: 'list' object is not an iterator
  • 那么我们是不是 只 实现了__next__方法就可以for in了呢?不可以!
# 自定义Person类,并实现了__next__方法,从而实现使用next()函数进行调用
# 只实现__next__方法时,即不是Iterable也不是Iterator
class Person:
    def __init__(self):
        self.result = 0

    def __next__(self):
        self.result += 1
        if self.result >= 3:
            raise StopIteration("停止遍历")
        return self.result

p1 = Person()
print(next(p1))  # 1
print(next(p1))  # 2
# print(next(p))  # 抛出StopIteration异常: 停止遍历


p2 = Person()
from collections.abc import Iterable, Iterator
print(isinstance(p2, Iterable))  # False
print(isinstance(p2, Iterator))  # False


for i in p2:  # 抛出TypeError异常: 'Person' object is not iterable
    print(i)  # 1 2

4.3 为什么for in不会抛出StopIteration异常?

  • 因为底层帮我们实现了,当解释器检测到__next__方法抛出StopIteration异常时,就停止循环了。
  • next()方法不会帮我们处理StopIteration异常

5. 哪些系统类型是可以for in遍历的?

  • int类型、bool类型:不可以,因为它们即没有实现__getitem__,也没有实现__iter__
  • str类型、list类型、set类型、dict类型、tuple类型:可以,因为它们实现了__iter__

6. 自定义类型如何实现for in?

三种方式:

  • 实现__iter__方法,从而变成Iterable类型
  • 实现__iter__方法和__next__方法,从而变成iterator类型
  • 实现__getitem__方法,只要实现了__getitem__方法,就可以被**【视为可迭代的】**,注意是视为,并不是真正意义上的Iterable可迭代对象!!
    • 实现了__getitem__方法的对象被**【视为可迭代对象】,并不是真正的Iterable类型**。Python的内置函数iter()会尝试调用__iter__方法获取迭代器,如果没有实现__iter__方法,那么Python会尝试调用__getitem__方法,从索引0开始获取元素,直到IndexError为止。因此,只要对象实现了__getitem__方法,就可以被视为可迭代对象
    • 所以,__getitem__执行的优先级比__iter__要低。

实现__iter__方法

  • 注意,因为没有实现__next__方法,所以不能使用next(),会报错
# 方式一:只实现__iter__方法,所以此时Person对象是可迭代对象
class Person:
    def __init__(self):
        self.result = 0
    def __iter__(self): # 注意__iter__方法必须返回迭代器对象
        return iter([1,2,3,4,5])
p = Person()
# print(next(p)) # TypeError: 'Person' object is not an iterator
for i in p:
    print(i)
from collections.abc import Iterable, Iterator
print(isinstance(p, Iterator))  # False
print(isinstance(p, Iterable))  # True
# 1
# 2
# 3
# 4
# 5

实现__iter__方法和__next__方法

  • 因为实现这两个方法就是迭代器了,迭代器是一定有__next__方法的,所以可以使用next()调用。
# 方式二:必须同时实现__iter__和__next__才是迭代器对象
class Person:
    def __init__(self):
        self.result = 0

    def __iter__(self):
        print("iter")
        self.result = 0 # 回退结束条件
        return self

    def __next__(self):
        self.result += 1
        if self.result >= 6:
            raise StopIteration("停止遍历")
        return self.result

p1 = Person()
for i in p1:
    print(i)
from collections.abc import Iterator, Iterable
print(isinstance(p1, Iterator)) # True
print(isinstance(p1, Iterable)) # True

p2 = Person()
print(next(p2)) # 1
print(next(p2)) # 2
print(next(p2)) # 3

实现__getitem__方法

# 方式三:只实现__getitem__方法,此时即非迭代器也非可迭代对象
class Person:
    def __init__(self):
        self.result = 0
    def __getitem__(self, item):
        self.result +=1
        if self.result >=6:
            raise StopIteration("停止遍历")
        return self.result
p = Person()
# print(next(p)) # TypeError: 'Person' object is not an iterator
for i in p:
    print(i)
# 1
# 2
# 3
# 4
# 5
from collections.abc import Iterable, Iterator
print(isinstance(p, Iterator))  # False # 判断是否是迭代器对象
print(isinstance(p, Iterable))  # False # 判断是否是可迭代对象
# 证明__iter__的优先级比__getitem__要高,只会打印“iter”并报错
class Person:
    def __init__(self):
        self.result = 0
    def __getitem__(self, item):
        print("getitem") 
    def __iter__(self):
        print("iter") # iter
p = Person()
for i in p: # TypeError: iter() returned non-iterator of type 'NoneType'
    print(i)

7. 为什么Python要给出iterable、iterator两种数据类型?为什么不直接只定义iterator呢?

from collections.abc import Iterable, Iterator

f = open("data.txt")
print(isinstance(f, Iterator))  # True,这说明f是个迭代器
print(isinstance(f, Iterable))  # True
# 我们知道这个f是可以放到for in中进行遍历的
for i in f:
    pass
print(f.read())  # 我们发现这里打印是空的,这就说明当我们对f这个迭代器进行遍历完成后,再使用read()获取里面内容是获取不到的


lt = [1, 2, 3]  # list是可迭代对象
for i in lt:
    pass
print(lt)  # 而这里会打印[1, 2, 3],遍历完list依旧在!

my_lt = iter([1, 2, 3]) # 将可迭代类型的数据转成迭代器
for i in my_lt:
    pass
print(list(my_lt))  # [],这里打印的是空的,说明迭代器遍历完后,里面的内容就没有了


f.close()
  • 说明迭代器是有状态的,而且是状态保持的,所以当迭代器进行循环完后,相当于指针是指向最后的。
  • 送代器是不能回退的,迭代器提供了一种惰性的访问方式。

8. 正视__getitem__

  • __getitem__方法的作用是使类的实例对象表现得像一个序列(如列表、元组等)或映射(如字典)一样可索引。
  • 即,当在一个类中定义了__getitem__方法时,该类的实例对象可以使用索引运算符([])来访问元素。
  • 而实现了__getitem__方法的对象是Sequence类型。
    • 在Python中,collections.abc模块定义了__getitem__方法的抽象基类,名为Sequence。如果一个类实现了__getitem__方法,那么它就可以被认为是Sequence类型,也就是可下标访问的,即"subscriptable"。
    • 但是需要注意的是,Python并没有一个名为"subscriptable"的内置类型或者抽象基类。“subscriptable"这个词通常用于描述实现了__getitem__方法的对象。在类型检查时,你可以使用collections.abc.Sequence来检查一个对象是否实现了__getitem__方法,即是否是"subscriptable”。
    • "subscriptable"的意思是可有下标的意思。
class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    # 使用__getitem__魔法函数加强class类型的Company,使其视为iterable可迭代类型及subscriptable
    def __getitem__(self, item):
        return self.employee[item]


company = Company(["tom", "bob", "jane"])
for em in company:
    print(em)
    
print(company[1])

company1 = company[:2]
for em in company1:
    print(em)

print(len(company1))

9. 结论

  • 一个对象是Iterable可迭代对象,则一定可以使用for ... in ...进行遍历
  • 反过来说,如果一个对象可以使用for ... in ...进行遍历,则这个对象不一定是可迭代对象,因为实现__getitem__方法也可以通过for ... in ...进行遍历。
  • iterator对象是一定可以使用next()函数进行访问的。
  • 能通过next()函数访问的不一定是Iterable对象。因为能进行next()函数访问只有一个要求就是实现__next__方法。
  • 而iterator对象要求必须同时实现:__iter____next__
  • 要让对象使用iter()函数转成对应的迭代器对象,则此对象要么实现__getitem__方法,要么实现 __iter__方法,要么是个可调用方法。

不用点赞,因为你看过就会了,别人没看过就永远不知所以然!

文章来源:https://blog.csdn.net/JENREY/article/details/134946888
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。