Python函数——列表推导式、生成器与迭代器
列表推导式
产生背景
现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
,要求你把列表里的每个值加1,你怎么实现?
第一种方法:
a = [1,3,4,6,7,7,8,9,11] for ind, val in enumerate(a): a[ind] += 1 print(a)
第二种方法:
a = [1,3,4,6,7,7,8,9,11] print(list(map(lambda x: x+1, a)))
列表推导式:
a = [1,3,4,6,7,7,8,9,11] print([i+1 for i in a])
使用列表推导式可简化代码。用法如下
例一:30以内所有能被3整除的数
print([i for i in list(range(31)) if i % 3 == 0]) #使用if 表达式
例二:30以内所有能被3整除的数的变为平方,否则乘以2
# 使用三元表达式 print([i*i if i % 3 == 0 else i*2 for i in list(range(31))])
练习题:
""" 例1: 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母 例2: 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表 例3: 求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]] """ str_li = ['abc', 'cd', 'xy'] print([x.upper() for x in str_li if len(x) >= 3]) print([(x, y) for x in list(range(6)) if x % 2 == 0 for y in list(range(6)) if y % 2 == 1]) M = [[1,2,3],[4,5,6],[7,8,9]] print([row[2] for row in M])
字典推导式
例一:将一个字典的key和value对调
# mcase = {'a': 10, 'b': 34} print({mcase[k]: k for k in mcase})
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,
如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?
这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成(),
就创建了一个generator:
生成器的创建方式
1. 类似于列表生成式()
2. 函数中使用yield
函数有了yield之后
1. 函数调用之后就得到了一个生成器,
2. return 在生成器里,代表生成器的中止,直接报错
3. yield 返回数据 ,并冻结当前的执行过程 。。
类似于列表生成式()创建
L = [x * x for x in range(10)] gen_L = (x * x for x in range(10)) # 生成器存放计算公式 print(L) print(gen_L) print(next(gen_L)) # 取值 print(next(gen_L)) """ [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] <generator object <genexpr> at 0x0000000001DFDF10> generator 就是生成器的意思 0 1 """
generator保存的是算法,每次调用next(g)
就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误,使用for循环可以解决异常
g = (x * x for x in range(10)) for n in g: print(n)
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
def fib(max): n, a, b = 0, 0, 1 while n < max: print('before yield') yield b # 把函数的执行过程冻结在这一步,并且把b的值 返回给外面的next() print(b) a, b = b, a + b n = n + 1 return 'done' f = fib(15) # turn function into a generator next(f) # first time call next() next 唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield next(f) # first time call next() """ before yield 1 before yield """
注意a, b = b, a + b 相当于
a, b = 0, 1 a, b = b, a + b print(a, b) a, b = 0, 1 t = a a = b b = t + b print(a, b) """ 1 1 1 1 """
函数方式创建
def range2(n): count = 0 while count < n : print(count) stop_flag = yield count #中断并返回 return count if stop_flag == 'stop': print('stop iteration...') break count += 1 b = range2(5) # 得到生成器 next(b) next(b) """ 0 Traceback (most recent call last): File "C:/Users/jingjing/PycharmProjects/py3Project/路飞/第二模块/函数练习/生成器.py", line 53, in <module> next(b) StopIteration: 0 """
生成器常用方法
next() # 唤醒生成器并继续执行 send("stop") """ 1. 唤醒并继续执行 2. 发送一个信息到生成器内部 注意生成器在刚开始,函数没有执行到yield成为挂起状态时,不能调用send("stop") 只能send('None')相当于next()使函数执行到yield """
send使用举例
def range2(n): count = 0 while count < n: print('count', count) count += 1 sign = yield count # return if sign == 'stop': print("---sign", sign) break print('sin...', sign) return 3333 new_range = range2(3) next(new_range) new_range.send(None) new_range.send("stop") """ count 0 sin... None count 1 ---sign stop Traceback (most recent call last): File "C:/Users/jingjing/PycharmProjects/py3Project/路飞/第二模块/函数练习/函数生成器.py", line 22, in <module> new_range.send("stop") StopIteration: 3333 """
生产者与消费者问题
import time def consume(name): print("%s 准备吃包子啦!" % name) while True: y = yield print("包子[%s]来了,被[%s]吃了!" % (y, name)) def producer(name): c = consume(name) next(c) # 函数执行到yield for i in range(1, 3): time.sleep(1) print("做了{}个包子!".format(i)) c.send(i) # 把i传到yield producer('qian')
""" qian 准备吃包子啦! 做了1个包子! 包子[1]来了,被[qian]吃了! 做了2个包子! 包子[2]来了,被[qian]吃了! """
日志记录
def logger(filename): """ 日志方法 :param filename: log filename :param channel: 输出的目的地,屏幕(terminal),文件(file),屏幕+文件(both) :return: """ print('start logger') while True: msg = yield print("msg", msg) l = logger('USER.TXT') l.__next__() l.send('hi') l.send('hi,file')
计算移动平均值
# 必须先用next再用send def average(): total=0 #总数 day=0 #天数 average=0 #平均数 while True: day_num = yield average #average=0 print('average', average) total += day_num day += 1 average = total/day avg=average() #直接返回生成器 next(avg)#激活生成器,avg.send(None),什么都不传的时候send和next的效果一样 print(avg.send(10)) print(avg.send(20))#send 1.传值 2.next print(avg.send(30)) """ average 0 10.0 average 10.0 15.0 average 15.0 20.0 """
带装饰器的计算移动平均值
# 让装饰器去激活 def wrapper(func): def inner(*args, **kwargs): print('execute wrapper') a = func(*args, **kwargs) next(a) return a return inner @wrapper def average(): total=0 #总数 day=0 #天数 average=0 #平均数 while True: day_num = yield average #average=0 print('average', average) total += day_num day += 1 average = total/day avg = average() print(avg.send(10)) print(avg.send(20))#send 1.传值 2.next print(avg.send(30)) """ execute wrapper average 0 10.0 average 10.0 15.0 average 15.0 20.0 """
yield from
def gen1(): for c in 'AB': yield c for i in range(3): yield i print(gen1()) print(list(gen1())) def gen2(): yield from 'AB' # 相当于 for c in 'AB': yield c yield from range(3) print(gen2()) print(list(gen2()))
处理异常
def fib(max): n, a, b = 0, 0, 1 while n < max: print('before yield') yield b # 把函数的执行过程冻结在这一步,并且把b的值 返回给外面的next() a, b = b, a + b n = n + 1 return 'done' g = fib(6) while True: try: x = next(g) print('g:', x) except StopIteration as e: print('Generator return value:', e.value) break
生成器小结
生成器的创建方式
1. 类似于列表生成式()
2. 函数中使用yield
优点:节省内存空间
函数有了yield之后
1. 函数调用之后就得到了一个生成器,
2. return 在生成器里,代表生成器的中止,直接报错
注意
python3 range相当于python2 xrange 生成器
python2 range相当于list
yield vs return
return 返回并中止function
yield 返回数据 ,并冻结当前的执行过程 。。
生成器常用方法
1.next()
2.send("stop")
"""
1. 唤醒并继续执行
2. 发送一个信息到生成器内部
注意生成器在刚开始,函数没有执行到yield成为挂起状态时,不能调用send("stop")
只能send('None')相当于next()使函数执行到yield
"""
迭代器
可迭代对象与迭代器
迭代:可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代。就像for循环一样取值。
可迭代协议:可以被迭代要满足要求的就叫做可迭代协议。内部实现了__iter__方法
iterable:可迭代的------对应的标志
字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
from collections import Iterable print(isinstance([], Iterable)) print(isinstance(123, Iterable)) print(isinstance('345', Iterable)) # """ # True # False # True # """
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了
迭代器协议:内部实现了__iter__,__next__方法。
迭代器的优点:如果用了迭代器,节约内存,方便操作,生成器是一种迭代器(Iterator)。
可迭代和迭代器的相同点:都可以用for循环
可迭代和迭代器的不同点:就是迭代器内部多实现了一个__next__方法
判断迭代器和可迭代的方法:
第一种:判断内部是不是实现了__next__方法
print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__
第二种:
Iterable 判断是不是可迭代对象
Iterator 判断是不是迭代器
from collections import Iterable from collections import Iterator #比如给一个字符串 s='abc' print(isinstance(s,Iterable))#isinstance判断类型的 print(isinstance(s,Iterator))
判断range函数和map函数
深入了解Iterator对象
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,
直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,
只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
迭代器小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python3的for
循环本质上就是通过不断调用next()
函数实现的,例如:
for x in [1, 2, 3, 4, 5]: pass
等价于
# 首先获得Iterator对象: iter_object = iter([1, 2, 3, 4, 5]) # 循环: while True: try: # 获得下一个值: x = next(iter_object) except StopIteration: # 遇到StopIteration就退出循环 break