一、迭代器
1、先来讲讲什么是可迭代对象
字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的。
2、怎么判断是不是一个可迭代对象
判定方法:内部含有‘__iter__’方法的数据就是可迭代对象
可迭代对象的种类:list str tuple set dict range() 文件句柄
s1 = '我叫王大锤' print(dir(s1)) # 查看对象S1中变量、方法 print('__iter__' in dir(s1)) # 判断__iter__方法是否在对象S1的所有方法中,存在返回true,不存在返回false
3、怎么判断这个对象是不是迭代器
判定方法:内部含有__iter__方法的并且含有__next__方法的就是迭代器
# 判断是否是迭代器 f1 = open('a.txt', encoding='utf-8') print('__iter__' in dir(f1)) # 判断是否有__iter__方法 print('__next__' in dir(f1)) # 判断是否有__next__方法 f1.close()
迭代器就是在迭代对象的基础上多了个__next__方法。既然这样他们肯定是可以互相转换的
转换方法:
可迭代对象 ----> 迭代器 :可迭代对象 . __next__()
# 循环输出每个元素 # 第一种方法 l1 = [1, 2, 3, '大锤'] # 这是个迭代对象 iter1 = iter(l1) # 这一步也可以用 l1.__iter__() 表示 print(iter1) # 输出iter1的内存地址 print(iter1.__next__()) # 1 print(iter1.__next__()) # 2 print(iter1.__next__()) # 3 print(iter1.__next__()) # 大锤 print(iter1.__next__()) # 超出元素个数,会报错 # 第二种方法 l1 = [1, 2, 3, '大锤'] for i in l1: # 循环输出每个元素 print(i) # 第三种方法
l1 = [1, 2, 3, '大锤']
for i in l1.__iter__(): # 循环输出每个元素 print(i)
4、迭代器的优点
非常非常节省内存:在循环时,同一时刻在内存中只出现一条数据,极大限度的节省了内存
他满足惰性机制:不访问__next__() 就没有值
一次性取值,只能按顺序取
5、为什么要有for循环
为什么要有for循环?
# 利用下表方式循环输出 l = [1, 2, 3, '大锤'] index = 0 while index < len(l): print(l[index]) index += 1 # 上述方法用下表就可以循环输出,为什么要用迭代器呢?
原因如下:
序列类型字符串:列表,元组都有下标,用上述的方式访问是可以的。
但是非序列类型:字典,集合,文件对象就无法实现了。
for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了
最重要的一点,转化成迭代器,在循环时,同一时刻在内存中只出现一条数据,极大限度的节省了内存~
for循环的优点:
将可迭代对象转化成迭代器;(可迭代对象.__iter__())
利用__next__方法一个一个取值;
利用异常处理终止循环。(不终止,取不到值就会报错)
用while循环模拟for循环:
l1 = [1, 2, 3, '大锤'] iter1 = l1.__iter__() while 1: try: print(iter1.__next__()) except StopIteration: # 利用异常处理终止循环 break
二、生成器
1、什么是生成器?
在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
生成器:自己用python代码写的迭代器 本质就是迭代器
2、Python中提供的生成器
生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
3、生成器函数(函数式写法的生成器)
判定方法:
只要函数中出现yield,那么 他就不是函数了,他是生成器函数。
yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
# 普通函数 def func1(): # 函数中出现了yield,此函数为生成器函数 print(111) print(222) yield 3 yield 4 yield 5 yield 6 yield 7 g = func1() # 生成器对象 print(g) # <generator object func1 at 0x10748fa98> 输出的是这个函数的内存地址 print(g.__next__()) # 等同于print(next(g)),输出结果为111 222 3 # 一个next 对应一个 yield print(g.__next__()) # 4 print(g.__next__()) # 5 print(g.__next__()) # 6
# 加了模块的函数 import time # 使用了模块的函数加了yield也是生成器函数 def func(): a = 1 print('现在定义了a变量') yield a b = 2 print('现在又定义了b变量') yield b g1 = func() print('g1 : ', g1) # 打印g1可以发现g1就是一个生成器 print('-'*20) # 我是华丽的分割线 print(next(g1)) # 结果:现在定义了a变量, 收到yield返回的值1 time.sleep(1) # sleep一秒看清执行过程 ,可以看出函数未被yield打断,可以继续执行 print(next(g1)) # 结果:现在定义了变量, 收到yield返回的值2
小总结:
return 结束函数 给函数返回值
yield 不会结束生成器函数,next 对应一个yield进行取值
4、生成器的好处
# 直接for 循环,一次性循环取出所有的值 def cloth(): for i in range(1, 201): print('当前被取出来的数是 %s' % (i)) cloth() # yield 返回,每次返回一个值 def cloth1(): for i in range(1, 201): yield '当前被取出来的数是 %s' % (i) g = cloth1() for i in range(5): print(g.__next__()) # 只输出1到5 五个数 for i in range(195): print(g.__next__()) # 只输出1到195 这几个数
由上面的例子可以看出来,yield可以根据需要取值,能够控制取值的个数,而且不会终止函数
5、send(与__next__类似)
# send 和 __next__的用法和对比 def func(): print(123) # 第三步 content = yield 1 # 第四步将1返回给第二步,即g._ # 第六步执行完,yield 1 接 print('=======', content) # 第七步 输出 = print(456) # 第八步 输出456 yield 2 #第九步 将值2返回给 g.__next__() g = func() # 第一步 ret = g.__next__() # 第二步 将这个函数转换成迭代器,这是 # 第四步执行完,ret接收到的值为 1 # 第九步执行完,ret接收到的值为 2 print('***', ret) # 第五步 输出 *** 1 ret = g.send('hello') # 第六步 给第四步的yield 1 print('***', ret) # 第十步 输出 *** 2
小总结:
send 获取下一个值的效果和next基本一致只是在获取下一个值的时候,给上一yield的位置(例子中的第六步给第四步传了一个值)
使用send的注意事项:
第一次使用生成器的时候 是用next获取
最后一个yield不能接受外部的值
三、列表推导式
1、列表推导式的模式
循环模式: [变量(加工后的变量) for 变量 in iterable]
筛选模式: [变量(加工后的变量) for 变量 in iterable if 条件]
# 循环模式:其实就是for循环的简写,循环输出所有 # 第一种 l1 = [i for i in range(1, 10)] print(l1) # [1, 2, 3, 4, 5, 6, 7, 8, 9] # 第二种 print(['python%s期' % i for i in range(1, 25)])
# 筛选模式 # 第一种 l1 = [i for i in range(10) if i % 2 == 0] print(l1) # [0, 2, 4, 6, 8] # 第二种 # 2.[10以内所有数的平方: 1,4,9,16,.....100] print([i*i for i in range(1, 11)]) # 2.[30以内能被3整除的数的平方:39,36,81....900] print([i**2 for i in range(1, 31) if i % 3 == 0]) # 3.过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母 l1 = ['alex', 'taibai', 'wusir', 're', 'ab'] print([i.upper() for i in l1 if len(i) > 3]) # 4.找到嵌套列表中名字含有两个‘e’的所有名字 # 用列表推导式实现 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] print([name for lst in names for name in lst if name.count('e') >= 2]) # ['Jefferson', 'Wesley', 'Steven', 'Jennifer'] # 用之前的老方法实现 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] l1 = [] for i in names: for i1 in i: if i1.count('e') >= 2: l1.append(i1) print(l1) # ['Jefferson', 'Wesley', 'Steven', 'Jennifer']
2、字典推导式
# 将一个字典的key和value对调 dic = {'a': 10, 'b': 34} dic2 = {dic[k]: k for k in dic} print(dic2) # {10: 'a', 34: 'b'} # 合并大小写对应的value值,将k统一成小写 dic = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} dic2 = {k.lower(): dic.get(k.lower(), 0) + dic.get(k.upper(), 0) for k in dic.keys()} print(dic2) # {'a': 17, 'b': 34, 'z': 3}
3、 集合推导式
# 计算列表中每个值的平方,自带去重功能 squared = {x**2 for x in [1, -1, 2]} print(squared) # {1, 4}
四、生成器表达式
g1 = (i*i for i in range(1, 3)) # 生成器表达式用() 括起来,列表推导式是用[],其他规则与列表推导式一致。 print(g1.__next__()) # 1 print(g1.__next__()) # 4 print(g1.__next__()) # 不会输出值,会报错,__next__方法只取1和2的值,不会取3的值
列表推导式,生成器表达式:
优点: 构造简单,一行完成
缺点: 不能排错,使用debug模式时,无法看到执行步骤
他不能构建复杂的数据结构。