一、迭代器
简介:
- 迭代是Python最强大的功能之一,是访问集合元素的一种方式。
- 迭代器是一个可以记住遍历的位置的对象。
- 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
- 迭代器有两个基本的方法:iter() 和 next()。
- 人工生成迭代器的方式,创建一个类实现内置方法__iter__()和__next__(),其中__iter__()方法为创建可迭代对象,__next__()为遍历可迭代对象,这两个方法都必须实现才能称之为迭代器。
- 一般的迭代器在使用时,一定要小心,一个可迭代对象只可以遍历一次,访问最后一个元素完毕之后,会抛出StopIteration异常,python中各个内置函数自动接收这个异常并处理,终止迭代,之后再访问该对象时,也不会报错,只是直接就终止访问了;原因为:迭代器遍历的时候,每次都会返回当前的状态,遍历到最后就把最后的状态记住了,无法返回到最开始的状态了。
- 使用迭代器会返回一个可迭代对象,并不会把所有的数据加载到内存中,在遍历元素的时候,使用python内置函数next()逐个读取数据,对于返回数据量比较大的情况下,可以极大节省内存。
- for循环在遍历迭代器的时候,实质是内部调用next()函数不断遍历迭代器,直到抛出StopIteration才终止。
- 如果想实现可反复访问的迭代器的话,需要实现的时候分离迭代对象与可迭代对象,下面code有列举。
Code示例:
# 迭代对象
class MyList(object): # 定义可迭代对象类
def __init__(self, num):
self.data = num # 上边界
def __iter__(self):
return MyListIterator(self.data) # 返回该可迭代对象的迭代器类的实例
# 可迭代对象
class MyListIterator(object): # 定义迭代器类,其是MyList可迭代对象的迭代器类
def __init__(self, data):
self.data = data # 上边界
self.now = 0 # 当前迭代值,初始为0
def __iter__(self):
return self # 返回该对象的迭代器类的实例;因为自己就是迭代器,所以返回self
def __next__(self): # 迭代器类必须实现的方法
while self.now < self.data:
self.now += 1
return self.now - 1 # 返回当前迭代值
raise StopIteration # 超出上边界,抛出异常
my_list = MyList(5) # my_list对象可以任意多次使用for循环遍历
my_list = MyListIterator(5) # my_list只能被遍历一次,之后再遍历就没有数据了
二、生成器
简介:
- 在 Python 中,使用了 yield 的函数被称为生成器(generator)。
- 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器;但是迭代器并不一定是生成器。
- 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
- 调用一个生成器函数,返回的是一个迭代器对象。
-
生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法。
-
生成器在迭代的过程中可以改变当前迭代值,而修改普通迭代器的当前迭代值往往会发生异常,影响程序的执行。
-
具有yield关键字的函数都是生成器,yield可以理解为return,返回后面的值给调用者;不同的是return返回后,函数会释放,而生成器则不会。在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直至遇到下一个yield。
Code示例:
# 实现可以重复迭代的功能:将__iter__函数实现为生成器就可以满足需求
class MyIterator(object):
def __init__(self, path):
self.path = path # 文件路径
def __iter__(self):
with open(self.path, encoding='utf-8') as f:
for line in f:
yield line.strip()
raise StopIteration
my_iterator = MyIterator('user_unknown_domains.dn') # my_iterator可以重复遍历,缺点就是每次都需要重新从文件中读取数据
# 自定义生成器
def myList(num):
now = 0
while now < num:
val = (yield now) # 可以重新设置生成器的值,使用send函数,将值设置到val中;除了设置新值的情况下,val的值会一直为None
print('val={}'.format(val))
now = now + 1 if val is None else val # 更换设置的新值
print('now={}'.format(now))
my_list = myList(10)
print(my_list) # 返回一个生成器对象
print(next(my_list)) # 从第一个yield的位置开始返回,之后每次返回的状态暂停到yield
print(next(my_list))
my_list.send(6)
print(next(my_list))
对于第二个函数的执行结果如下,请看图片内容
这块是好多初学者都很难想通的一个问题???,出现此问题的原因如下:
因为调用mylist之后,函数状态返回到第一次出现yield的位置,函数yield之后的部分只有下一次在执行next的时候,才能执行到
此外yield不止是实现生成器的一大利器,在python3实现协程的功能上也起到了很大的作用,实现了函数的上下文控制调用,感兴趣的可以研究一下协程