一、什么是可迭代对象?(底层实现逻辑,知道什么是可迭代对象就好,下方代码仅供理解,理解不了没关系,只要知道下面的文字描述就ok)
答:能够进行迭代逐一返回其成员项的对象成为可迭代对象。可迭代对象的例子包括
- 所有序列类型:如 list 、str和tuple
- 非序列类型:dict、set、文件对象
- 实现了__iter__()方法的任意对象(可迭代协议)
- 实现了序列语义的__getitem__()方法的任意对象
注意点 :实现了__getitem__()方法的被称为可迭代对象(支持迭代操作),但不是Iterable类型。
我们自己手写一个可迭代对象:
# 可迭代对象,迭代器,生成器
from collections.abc import Iterable, Iterator, Generator
# 可迭代对象 python里面什么叫迭代?通过循环,能够通过for循环进行迭代操作的对象
class Mylist:
def __iter__(self):
return iter([11, 22, 33, 44])
if __name__ == '__main__':
m = Mylist()
print(isinstance(m, Iterable))
# for循环不管是遍历元祖还是列表还是字典,首先做的是什么?首先调的是 m对象的iter方法,
# 然后遍历的是什么东西呢?遍历的是m这个对象里面的iter方法所返回的一个迭代器
for i in m:
print(i)
# 实现了__getitem__()方法
class Mylist2:
"""自定义序列类"""
li = [11, 22, 33, 44]
def __getitem__(self, item):
return self.li[item]
if __name__ == '__main__':
m2 = Mylist2()
for i in m2:
print(i)
print(isinstance(m2, Iterable))
二、什么是迭代器?
答:从应用层面上来讲,能够通过for循环进行迭代操作,也能够通过next方法进行迭代操作;
从原理层面上来说,实现了迭代器协议的对象(实现了__inter__方法),也实现了__next__方法。
- 1、实现了迭代器协议的对象,就是一个迭代器
- 2、所有的可迭代对象,都可以通过内置函数iter()转换为迭代器
- 3、迭代器对象能够使用 内置函数next进行迭代操作
- 4、所有的迭代器都是可迭代对象,因为迭代器协议包含了迭代协议
1、略
2、 由上图可以看出,res是可迭代对象,但是他不是一个迭代器,那怎么将他转换为迭代器呢?2上所述,可以通过内置函数iter方法转化,那我们试一下。
3、 迭代器对象能够使用 内置函数next进行迭代操作
如果继续next遍历一次,那么就会出来一个y
如果使用dic4.keys进行next迭代,那么就会报错,因为他不是一个迭代器,他只是一个可迭代对象。
注意:由上图看出来,迭代器Iterator是继承自Iterable,也有__iter__方法,所以说,可迭代对象不一定是可迭代器,但迭代器肯定是可迭代对象。
(1)我们通过dir内置函数方法查看这个可迭代对象有什么方法:
(2)__length_hint__这个方法是用来获取可迭代对象这个容器里面有多少数据
# 获取迭代器内部的数据长度
li1 = iter([11, 22, 33, 44])
print(li1.__length_hint__())
# 打印结果
4
(3) 通过next方法或者__next__()进行迭代操作。
(4)迭代到这个容器里面没有数据了,就会报错 StopIteration
(5)__setstate__() 这个方法是设置默认从哪里开始进行迭代
①
# 设置从索引为2的地方开始迭代
li2 = [11, 22, 33, 44]
li2.__setstate__(2)
# 打印结果
33
②这个时候,我们设置了从2开始进行迭代操作,然后我们通过for循环试试他打印的是多少
# 设置从索引为2的地方开始迭代
li2 = [11, 22, 33, 44]
li2.__setstate__(2)
for i in li2:
print('i的值为',i)
# 打印结果
33
i的值为33
i的值为44
很明显,我们通过__setstate__(2)更改了他迭代的起始位置。所以打印了33,44
③迭代之前获取到的数据的长度4,迭代之后获取到的数据长度3,当数据长度为0的时候,再去做迭代操作,就会抛出异常StopIteration
# 设置从索引为2的地方开始迭代
li1 = iter([111, 22, 33, 44])
print(li1.__length_hint__())
print(next(li1))
print(li1.__length_hint__())
# 打印结果
4
111
3
4、迭代器到底用来干什么的?(迭代器的实际运用)
读取文件里的内容,一行一行读取,放在迭代器里面,节省内存的开销。
如果想进行批量迭代的话,可以通过for循环
with open(r'C:\project\py01.py', 'r', encoding='utf-8') as f:
res = iter(f.readlines())
for i in res:
print(i)
三、什么是生成器?
生成器里面有哪些方法?__iter__和__next__方法是继承自 Iterator迭代器的,独有的三个方法send(往生成器里传入数据)、throw(往生成器里面抛异常)、close(关闭生成器)。
(1)生成器的定义
① 生成器的定义
② 生成器表达式
从上面打印结果可以看得出来,生成器支持next进行迭代操作(生成数据),也支持for循环遍历
# 生成器表达式
g1 = (i for i in range(10))
print(next(g1))
print(next(g1))
for i in g1:
print('i的值为', i)
# 打印结果
0
1
i的值为 2
i的值为 3
i的值为 4
i的值为 5
i的值为 6
i的值为 7
i的值为 8
i的值为 9
# 从上面打印结果可以看得出来,生成器支持next进行迭代操作(生成数据),也支持for循环遍历
③ 生成器函数: yield关键字,
生成器函数定义:在函数中通过使用关键字yield来实现生成器函数,不会执行里面的代码,返回的是一个生成器。
打断点调试的时候,发现不会走到func01方法里面,压根就不会调用这个函数,直接返回一个生成器。
--------------------------------------------------------------------------------------------------------------------------------
(2)生成器的迭代操作(生成数据)
但是假如我们通过next对生成器进行迭代操作(生成数据),我们打断电调试发现会执行到这个方法里面,但是执行到yield之后,并没有往下执行print("----------end----------")这一行代码,就直接从方法里出来了。
---------------------------------------------------------------------------------------------------------------------------------
我们打印这个next(res)发现这个值为 None,为什么因为这个yield 后面没有值,然后遇到yield也不回继续往下执行,所以不会打印111,222,333,end
def func01():
print('----------start----------')
yield
print('-----------111------------')
yield 'python'
print('-----------222------------')
yield 'java'
print('-----------333------------')
yield
print('-----------end------------')
res = func01()
print('第一次迭代', next(res))
# 打印结果
----------start----------
第一次迭代 None
--------------------------------------------------------------------------------------------------------------------------------
然后我们在生成器函数里 在yield 后面添加值的时候,我们看执行的结果。
接着往下执行:输出111,然后输出第二个yield后面的python,然后从方法中出来。
由此可以看出:有几个yield 这个生成器就能执行几次迭代操作。
--------------------------------------------------------------------------------------------------------------------------------
注意:这个yield关键字只能用在函数里面,如果用在其他地方就会报语法错误:SyntaxError: 'yield' outside function。
def func01():
print('----------start----------')
print('-----------end------------')
res = func01()
print(res)
# 打印结果
----------start----------
-----------end------------
None
# ------------------------------------------------------------------------
## 这个时候我们在两个print中间加个yield
def func01():
print('----------start----------')
yield
print('-----------end------------')
res = func01()
print(res)
# 打印结果
<generator object func01 at 0x0000020B67555EE0>
生成器进行迭代操作实际运用(特殊用法):
def func01():
for i in range(100):
yield i
g1 = func01()
# 1、使用next进行迭代操作
print(next(g1))
print(next(g1))
print(next(g1))
print(next(g1))
# 打印结果
0
1
2
3
(3)生成器内部数据交互
① 使用send方法进行迭代(send方法可以往生成器内部传递数据)
常用场景是根据我们给的数据,然后给我们生成新的数据出来。
def func01():
for i in range(100):
s = yield i
print('s值为:', s)
g1 = func01()
# 1、使用next进行迭代操作
# print(next(g1))
# print(next(g1))
# print(next(g1))
# print(next(g1))
# 2、使用send方法进行迭代
next(g1) # 启用生成器
print('-----------------------------------------------------')
res = g1.send('musen')
print('res:', res)
print('-----------------------------------------------------')
res = g1.send('java')
print('res:', res)
# 打印结果
-----------------------------------------------------
s值为: musen
res: 1
-----------------------------------------------------
s值为: java
res: 2
由此可以看出来,send里面传入的参数实际是给 yield前面的s的,yield后面的值 实际是遍历的值赋给res。
② 关闭生成器close
def func2():
res = {"data": None, "title": None}
for i in range(100):
s = yield res
res["data"] = s
res['title'] = '用例' + str(i)
g2 = func2()
next(g2)
print(g2.send('musen'))
print(g2.send('xiaojian'))
print(g2.send('yuze'))
# 关闭生成器
g2.close()
print(g2.send('wangqiang'))
# 打印结果
{'data': 'musen', 'title': '用例0'}
{'data': 'xiaojian', 'title': '用例1'}
{'data': 'yuze', 'title': '用例2'}
Traceback (most recent call last):
File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 24, in <module>
print(g2.send('wangqiang'))
StopIteration
Process finished with exit code 1
③ throw:主动往生成器内部抛异常(了解即可,没有实际运用场景,咱们还没有达到那种境界 哈哈哈)
g2.throw(ValueError) ,相当于在生成器内部执行 raise 异常类型
def func2():
res = {"data": None, "title": None}
for i in range(100):
s = yield res
res["data"] = s
res['title'] = '用例' + str(i)
g2 = func2()
next(g2)
print(g2.send('musen'))
print(g2.send('xiaojian'))
print(g2.send('yuze'))
# 关闭生成器
g2.close()
print(g2.send('wangqiang'))
g2.throw(ValueError)
# 打印结果
{'data': 'musen', 'title': '用例0'}
{'data': 'xiaojian', 'title': '用例1'}
{'data': 'yuze', 'title': '用例2'}
Traceback (most recent call last):
File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 23, in <module>
g2.throw(ValueError)
File "/Users/wangqiang/PycharmProjects/py09Class/复习/py09_03day/demo3_生成器特殊的方法.py", line 12, in func2
s = yield res
ValueError
(4)实战场景
1、实现一个生成{data:xxxx}的生成器
g2.send('zhanzhao'):执行顺序,先把'zhanzhao'赋值给s,然后再执行res['data']=s,再跑到yield的时候把res返回。
# 实现一个生成{data:xxxx}的生成器
def func2():
res = {'data': None}
for i in range(100):
s = yield res
res['data'] = s
g2 = func2()
# 启动生成器
next(g2)
# 向生成器中传入数据
res1 = g2.send('zhanzhao')
print(res1)
# 打印结果:
{'data': 'zhanzhao'}
(5)课后练习(附答案)
# 1、现在有一个列表 li = [11,21,4,55,6,67,123,54,66,9,90,56,34,22],
# 请将 大于5的数据过滤出来,然后除以2取余数,结果放到一个生成器中
# li = [11, 21, 4, 55, 6, 67, 123, 54, 66, 9, 90, 56, 34, 22]
# g1 = [i % 2 for i in li if i > 5]
# print(g1)
# 2、定义一个可以使用send传入域名,自动生成一个在前面加上http://,在后面加上路径/user/login的url地址,
# 生成器最多可以生成5个url,生成5条数据之后再去生成,则报错StopIteration
# 使用案例:
# 例如:
# res = g.send('www.baidu.com')
# # 生成数据res为:http://www.baidu.com/user/login'
# 方法1:
def func1():
res = None
for i in range(6):
s = yield res
res = "http://" + s + "/user/login"
g1 = func1()
# 启动生成器
next(g1)
print(g1.send("www.baidu.com"))
print(g1.send("www.guge.com"))
print(g1.send("www.kuaishou.com"))
# 方法2:
def func2():
address = None
url = f'(http://{address}/user/login)'
for i in range(6):
s = yield url
url = f'(http://{s}/user/login)'
g2 = func2()
# 1、使用next启动生成器
next(g2)
fin = g2.send('www.baidu.com')
print(fin)
fin2 = g2.send('www.baidu2.com')
print(fin2)
fin3 = g2.send('www.baidu3.com')
print(fin3)
fin4 = g2.send('www.baidu4.com')
print(fin4)
fin5 = g2.send('www.baidu5.com')
print(fin5)
fin6 = g2.send('www.baidu6.com')
print(fin6)
# 3、面试笔试扩展题
# 有一个正整数列表(数据是无序的,并且允许有相等的整数存在),
# 编写一个能实现下列功能的函数,传入列表array,和正整数X,返回下面要求的2个数据
# def func(array, x)
# '''逻辑代码'''
# return count, li
# 1、统计并返回在列表中,比正整数x大的数有几个(相同的数只计算一次),并返回-----返回值中的的count
# 2、计算列表中比正整数X小的所有偶数,并返回 -----------返回值中的li
def func(array, x):
null_list = []
for i in array:
if i > x:
if i not in null_list:
null_list.append(i)
li = [j for j in array if j < x and j % 2 == 0]
return null_list, len(null_list), li
print(func([10, 22, 33, 44], 22))