【疑问解答】魔法方法__iter__和__next__、__getitem__方法及其相关概念
一、for … in的机制
参考:
Python 中的 for 循环与可迭代对象介绍
迭代:一个可迭代对象是不能独立进行迭代的,Python中,迭代是通过for … in来完成的。凡是可迭代对象都可以直接用for… in…循环访问(for…in…只支持可迭代对象),这个语句其实做了两件事:第一件事是调用__iter__()获得一个可迭代器,第二件事是循环调用__next__()。
其实在执行 for 循环时,实际执行流程是这样的:
-> for i in a 相当于执行 iter(a)
-> 每次迭代时会执行一次 __next__ 方法,返回一个值
-> 如果没有可迭代的数据,抛出 StopIteration 异常,for 会停止迭代
a = [1, 2, 3]
for i in a:
do_something()
其实在python内部进行了类似如下的转换:
a = [1, 2, 3]
for i in iter(a):
do_something()
ps:for循环可以用while来替代,手动catch StopIteration:
iter_ = iter([1,2,3,4,1,2,3,4,"Done"]) # 将其变为迭代器本身
while True:
try:
print(next(iter_))
except StopIteration:
del iter_ # python3 中,循环内部变量的迭代器在循环完成后会被废除,Python2 不需要
break
注:当调用iter()函数的时候,生成了一个迭代器对象,要求__iter__必须返回一个实现了__next__的迭代器对象,我们就可以通过next()函数访问这个对象的下一个元素了,并且在你不想继续有迭代的情况下抛出一个StopIteration的异常(for语句会捕获这个异常,并且自动结束for)。
iter() 函数用来生成迭代器
next() 返回迭代器的下一个项目
总结:
- for…in…的右边只支持可迭代对象。
- for…in…可以捕获StopIteration的异常,并且自动结束for。
二、迭代器和可迭代对象、生成器
参考:
Python进阶——迭代器和可迭代对象有什么区别?生成器。
【Python魔术方法】迭代器(iter__和__next)
Python基础9——可迭代对象、迭代器、生成器
【Python魔术方法】生成器(yield表达式)
总结:
- 迭代器协议:一个对象要想使用 for 的方式迭代出容器内的所有数据,这就需要这个类实现「迭代器协议」。
- 迭代器(iterator):一个类如果实现了「迭代器协议」,就可以称之为「迭代器」。在 Python 中,实现迭代器协议就是要在类中实现以下 2 个方法:
__iter_:这个方法返回对象本身,即 self。
__next_:这个方法每次返回迭代的值,在没有可迭代元素时,抛出 StopIteration 异常。 - 可迭代对象( iterable):使如果只是实现了 __iter__(没有实现__next__),并且这个方法返回的是一个迭代器对象,那么这个类的实例就只是一个可迭代对象,因为它的迭代细节是交给了另一个类来处理。或实现了__getitem__,其实例化对象可以从0开始索引,例object[i]。
4.迭代区别: 可迭代对象可以一直重复迭代,因为它__iter__方法返回一个迭代器;而迭代器对象只能迭代一次。迭代器对象经过一次for后不能再次for,可迭代对象经过一次for,还可以重新for。
5.__iter__若返回的是自身,则是迭代器;若返回的是某个实现了迭代器的类的对象,则是可迭代对象。
6.可迭代对象可以用内置iter函数可以获取迭代器的对象,例如list、tuple、set、dict 类型。
class A:
# A是迭代器 因为它实现了 __iter__ 和__next__方法
def __init__(self, n):
self.idx = 0
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration() # 抛出for .. in 会捕获这个错误,并停止for .. in
class B:
# B不是迭代器 但B的实例是一个可迭代对象
# 因为它只实现了 __iter__
# __iter__返回了A的实例 迭代细节交给了A
def __init__(self, n):
self.n = n
def __iter__(self):
return A(self.n)
# a是一个迭代器 同时也是一个可迭代对象
a = A(3)
for i in a: # 输出0-2
print(i)
print(iter(a)) # <__main__.A object at 0x10eb95550>
for i in a: # 不会输出任何东西,迭代器只能迭代一次。
print(i)
# b不是迭代器 但它是可迭代对象 因为它把迭代细节交给了A
b = B(3)
for i in b: # # 输出0-2(可迭代对象可以一直重复迭代)
print(i)
# <__main__.A object at 0x10eb95450>
print(iter(b))
for i in b: # 输出0-2(可迭代对象可以一直重复迭代)
print(i)
python 异常处理 StopIteration
如何判断一个对象是可迭代对象或者迭代器对象?
可以使用collection.abs里面的Iterator和Iterable配合isinstance函数来判断一个对象是否是可迭代的,是否是迭代器对象。
from collections.abc import Iterable, Iterator
print(isinstance(object_name, Iterable)) # 返回True表明是可迭代对象
print(isinstance(object_name, Iterator)) # 返回True表明是迭代器对象
三、__getitem__方法
__getitem__(sekf, index):拦截索引运算符,当出现“实例名[index]”的实现时,Python会调用这个实例定义或者继承的__getitem__方法(如果有的话)。
__getitem__的实现有两种
- __getitem__把实例变成可迭代器(实际对象并不是可迭代对象或者迭代器对象)
- 使用下标获取类中对应元素值
__getitem__把实例变成可迭代对象(实际并不是)
from collections.abc import Iterable, Iterator
class Company(object):
def __init__(self, employee_list):
self.employee = employee_list
def __getitem__(self, item):
print('uses getitem') # 判断是否使用getitem
return self.employee[item]
company = Company(["tom", "bob", "jane"])
for em in company:
print(em)
for em in company:
print(em)
print(isinstance(company, Iterable)) # 返回True表明是可迭代对象
print(isinstance(company, Iterator)) # 返回True表明是迭代器对象
输出:
uses getitem
tom
uses getitem
bob
uses getitem
jane
uses getitem
uses getitem
tom
uses getitem
bob
uses getitem
jane
uses getitem
False
False
注:__getitem__在for中会多循环一次。
__getitem__使用下标获取类中对应元素值
参考:class类中__getitem__的作用及其使用方法
class Test():
def __init__(self):
self.a=[1,2,3,4,5]
def __getitem__(self,idx):
return(self.a[idx])
data=Test()
print(data)
print(data[0])
输出:
<__main__.Test object at 0x000002BBB3256DF0>
1
四、__iter__和__getitem__区别
参考:【Python】__iter__和__getitem__区别
参考:Python __getitem__和自定义的迭代器
- __iter__方法的作用是让对象可以用for … in循环遍历
- __getitem__方法是让对象可以通过“实例名[index]”的方式访问实例中的元素,也可让对象可以用for … in循环遍历。
- 当两者同时存在时,实例名[index]直接进入__getitem__()函数,for则直接进入的是__iter__函数,也就意味着__iter__是优先读取的。