一 生成器与yield

若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象

  1. >>> def my_range(start,stop,step=1):
  2. ... print('start...')
  3. ... while start < stop:
  4. ... yield start
  5. ... start+=step
  6. ... print('end...')
  7. ...
  8. >>> g=my_range(0,3)
  9. >>> g
  10. <generator object my_range at 0x104105678>

生成器内置有iternext方法,所以生成器本身就是一个迭代器

  1. >>> g.__iter__
  2. <method-wrapper '__iter__' of generator object at 0x1037d2af0>
  3. >>> g.__next__
  4. <method-wrapper '__next__' of generator object at 0x1037d2af0>

因而我们可以用next(生成器)触发生成器所对应函数的执行,

  1. >>> next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数
  2. start...
  3. 0
  4. >>> next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...
  5. 1
  6. >>> next(g) # 周而复始...
  7. 2
  8. >>> next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代
  9. end...
  10. Traceback (most recent call last):
  11. File "<stdin>", line 1, in <module>
  12. StopIteration

既然生成器对象属于迭代器,那么必然可以使用for循环迭代,如下:

  1. >>> for i in countdown(3):
  2. ... print(i)
  3. ...
  4. countdown start
  5. 3
  6. 2
  7. 1
  8. Done!

有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

二 yield表达式应用

在函数内可以采用表达式形式的yield

  1. >>> def eater():
  2. ... print('Ready to eat')
  3. ... while True:
  4. ... food=yield
  5. ... print('get the food: %s, and start to eat' %food)
  6. ...

可以拿到函数的生成器对象持续为函数体send值,如下

  1. >>> g=eater() # 得到生成器对象
  2. >>> g
  3. <generator object eater at 0x101b6e2b0>
  4. >>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值
  5. Ready to eat
  6. >>> g.send('包子')
  7. get the food: 包子, and start to eat
  8. >>> g.send('鸡腿')
  9. get the food: 鸡腿, and start to eat

针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值,g.send(None)等同于next(g)。

我们可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作,如下

  1. def init(func):
  2. def wrapper(*args,**kwargs):
  3. g=func(*args,**kwargs)
  4. next(g)
  5. return g
  6. return wrapper
  7. @init
  8. def eater():
  9. print('Ready to eat')
  10. while True:
  11. food=yield
  12. print('get the food: %s, and start to eat' %food)

表达式形式的yield也可以用于返回多次值,即变量名=yield 值的形式,如下

  1. >>> def eater():
  2. ... print('Ready to eat')
  3. ... food_list=[]
  4. ... while True:
  5. ... food=yield food_list
  6. ... food_list.append(food)
  7. ...
  8. >>> e=eater()
  9. >>> next(e)
  10. Ready to eat
  11. []
  12. >>> e.send('蒸羊羔')
  13. ['蒸羊羔']
  14. >>> e.send('蒸熊掌')
  15. ['蒸羊羔', '蒸熊掌']
  16. >>> e.send('蒸鹿尾儿')
  17. ['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']

三 三元表达式、列表生成式、生成器表达式

3.1 三元表达式

三元表达式是python为我们提供的一种简化代码的解决方案,语法如下

  1. res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值

针对下述场景

  1. def max2(x,y):
  2. if x > y:
  3. return x
  4. else:
  5. return y
  6. res = max2(1,2)

用三元表达式可以一行解决

  1. x=1
  2. y=2
  3. res = x if x > y else y # 三元表达式

3.2 列表生成式

列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,语法如下

  1. [expression for item1 in iterable1 if condition1
  2. for item2 in iterable2 if condition2
  3. ...
  4. for itemN in iterableN if conditionN
  5. ]
  6. #类似于
  7. res=[]
  8. for item1 in iterable1:
  9. if condition1:
  10. for item2 in iterable2:
  11. if condition2
  12. ...
  13. for itemN in iterableN:
  14. if conditionN:
  15. res.append(expression)

针对下述场景

  1. egg_list=[]
  2. for i in range(10):
  3. egg_list.append('鸡蛋%s' %i)

用列表生成式可以一行解决

  1. egg_list=['鸡蛋%s' %i for i in range(10)]

3.3 生成器表达式

创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式,与列表生成式的语法格式相同,只需要将[]换成(),即:

  1. expression for item in iterable if condition

对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象

  1. >>> [x*x for x in range(3)]
  2. [0, 1, 4]
  3. >>> g=(x*x for x in range(3))
  4. >>> g
  5. <generator object <genexpr> at 0x101be0ba0>

对比列表生成式,生成器表达式的优点自然是节省内存(一次只产生一个值在内存中)

  1. >>> next(g)
  2. 0
  3. >>> next(g)
  4. 1
  5. >>> next(g)
  6. 4
  7. >>> next(g) #抛出异常StopIteration

如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成

  1. with open('db.txt','rb') as f:
  2. nums=(len(line) for line in f)
  3. total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果=