什么是生成器,本质上就是我们自己写的函数
可以理解为一种数据类型,自动实现了迭代器协议,所以生成器就是可迭代对象
#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码,只有在调用.__next__ 方法的时候再执行函数内部代码
生成器函数:常规函数定义,但是使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在这个结果中间挂起函数的状态,以便下次从它离开的地方继续执行
def test(): yield 1 yield 2 yield 3 g=test() print('来自函数',g)
运行结果:
来自函数 <generator object test at 0x0000000002812F10>
判断它是否是一个迭代器?
print(isinstance(g,Iterable)) print(isinstance(g,Iterator))
运行结果:
True
True
由此可见,生成器就是一个迭代器,可以按照迭代器的方法进行取值
def test(): print('a') yield 1 print('b') yield 2 print('c') yield 3 g=test() print(g.__next__())
运行结果:
a
1
#由此可见,只有在调用.__next__方法的时候才会执行函数内部的代码,而且碰到yield之后就会停止,函数状态就会保留在yield那里,等待下一次的调用
但是注意了,取生成器的值除了.__next__之外,还有send也可以取,我们看一个例子:
def generator(): print(123) content=yield 1 print('=====',content) print(456) age=yield 2 g=generator() ret=g.__next__() print('***',ret) ret=g.send('hello') print('***',ret)
运行结果:
123
*** 1
===== hello
456
*** 2
#为什么多了个hello出来呢?
#像我们之前说的,生成器函数遇到yield之后就会停止继续往下执行,在函数中,先运行=号右边的,再执行=号左边的,
#因为content=yield 1 执行到了 yield 1 之后就不再执行了,所以content=None,然后再进行send(‘hello’)的时候就将hello传给了content,所以这就是为什么多了一个hello的原因
#特别要注意一点,yield应该作为函数的结尾,如果在yield 后面再加代码,执行.__next__就会有问题
#在send前面必须要调用一次.__next__
例子:移动取平均值
#每传一个就计算一次平均值,可以利用send
def avger(): sum=0 avg=0 count=0 while True: num=yield avg sum+=num count+=1 avg=sum/count ret=avger()##利用avger生成器函数生成一个生成器 ret.__next__() avg1=ret.send(10)#接受生成器的返回值 avg1=ret.send(20) print(avg1)
运行结果:
15.0
#预激生成器的装饰器
主要的作用就是不需要生成器自己调用.__next__
def init(func): def inner(*args,**kwargs): g=func(*args,**kwargs) g.__next__() return g return inner @init def avger(): avg=0 count=0 sum=0 while True: num=yield avg sum+=num count+=1 avg=sum/count avg1=avger()#产生一个生成器,因为已经预激活了,所以直接就可以send了,不需要像之前加 avg1.__next__()
ret=avg1.send(10)
ret=avg1.send(20)
print(ret)
如何循环两个字符串:
def test(): a='abcdefg' b='1234567' yield from a yield from b a=test() for i in a: print(i )
迭代器作业:处理文件,用户指定要查找的文件和内容,将文件中包含要查找内容的,每一行都输出到屏幕
def check_file(filename,aim): with open(filename,encoding='utf-8') as f: for line in f: if aim in line : yield line check=check_file('filetest.txt','zhen') for i in check: print(i)
写生成器,从文件中读取内容,在每一次读取到内容之前加上
*** 之后再返回给用户
def check_file(filename): with open(filename,encoding='utf-8') as f: for line in f: yield '***'+line check=check_file('filetest.txt') for i in check: print(i)
生成器表达式: 类似于列表推导
三元表达式:
返回值在条件前面 'SB' if name == 'children' else '帅哥'
name='children' res= 'SB' if name == 'alex' else '帅哥' print(res)
运行结果
SB
列表解析:
一个正常的for循环函数
egg_list=[] for i in range(10): egg_list.append('鸡蛋%s' %i) print(egg_list)
但是这个函数可以一条语句写完,这个就是列表解析
egg=['鸡蛋%s' %i for i in range(10)] print(egg)
注意格式:输出内容在前,然后循环在后
列表解析也可以套用三元表达式
egg=['鸡蛋%s' %i for i in range(10) if i >5 ] print(egg)
输出i大于5 的
['鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
例一:30以内所有能被3整除的数
count=[i for i in range(30) if i%3==0] print(count)
运行结果:
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
例二:30以内所有能被3整除的数的平方
count=[i**2 for i in range(30) if i%3==0] print(count)
运行结果:
[0, 9, 36, 81, 144, 225, 324, 441, 576, 729]
例三:找到嵌套列表中名字含有两个‘e’的所有名字
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
num=[i for name in names for i in name if i.count('e')==2 ] print(num)
运行结果:
['Jefferson', 'Wesley', 'Steven', 'Jennifer']
字典推导式
例一:将一个字典的key和value对调
mcase = {'a': 10, 'b': 34} mcase_dic={mcase[k]:k for k in mcase} print(mcase_dic)
运行结果:
{10: 'a', 34: 'b'}
例二:合并大小写对应的value值,将k统一成小写
mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()} print(mcase_frequency)
运行结果:
{'a': 17, 'b': 34, 'z': 3}
集合推导式
例:计算列表中每个值的平方,自带去重功能
squared = {x**2 for x in [1, -1, 2]} print(squared)
例1: 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
li=['asdasda','asd','da','da2ea','dd','aasda']
count=[i for i in li if len(i)>=3]
print(count)
运行结果:
['asdasda', 'asd', 'da2ea', 'aasda']
例2: 求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表
count=[(x,y) for x in range(5) if x%2==0 for y in range(5) if y%2!=0]
print(count)
运行结果:
count=[(x,y) for x in range(5) if x%2==0 for y in range(5) if y%2!=0]
print(count)
例3: 求M中3,6,9组成的列表 M = [[1,2,3],[4,5,6],[7,8,9]]
M = [[1,2,3],[4,5,6],[7,8,9]]
count=[i for li in M for i in li if i in [3,6,9]]
print(count)
列表解析如何和生成器结合起来?
把[]改为()即可
laomuji=('鸡蛋%s' %i for i in range(10))
这样就可以迭代了
这样的就叫生成器表达式
生成器只能遍历一次
如何从生成器取值呢?
for循环
.__next__,
总结:
把列表解析的[]换成()就是生成器表达式了
列表解析与生成器表达式都是一种遍历的编程方式,但是生成器表达式更节省内存
Python 不但使用迭代器协议,让for循环变得更加通用。大部分内置函数也是使用迭代器访问对象的。例如sum函数,map函数,reduce函数
print(sum(x**2 for x in range(4))) #不需要先生成列表,节省内存空间
而不用多次一句先构造一个列表
print(sum([x**2 for x in range(4)]))#因为先要生成列表,所以需要更大的内存
文件监控例子:
#方法一: f=open('filetest.txt','r',encoding='utf-8') while True: line=f.readline() if line: print(line.strip())
#方法二:函数形式 def tail(filename): f=open(filename,encoding='utf-8') while True: line=f.readline() if line: print(line.strip()) tail('filetest.txt')
但是这样就会一直占用内存空间,浪费内存
而且如果打印结果不一样就无法满足了
比如A想在打印前面加****
B想在打印后面加——————
这样就无法满足了,这个时候就可以用yeid
为什么不用return呢?因为return 代表着函数结束
而且用还可以做定向输出,只打印自己想要的
#方法三 def tail(filename): f=open(filename,encoding='utf-8') while True: line=f.readline() if line.strip(): yield line.strip() g=tail('filetest.txt') for i in g: if 'chao' in i : print('***',i)
生成器面试题:
def demo():
for i in range(4):
yield i
g=demo()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
打印结果:
[0, 1, 2, 3]
[]
为什么g2为空呢?因为生成器只能取一次值,取完之后就为空了
虽然g2是for 循环g1 但是g1是for循环g的,所以g的值被g1取完之后,g2再取就为空了