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"的意思是可有下标的意思。
- 在Python中,
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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!