16.3-匿名函数、生成器
有些人看起来整天面带笑容,并不是因为他们事事顺利,只是他们比你敢于面对问题、善于遗忘不幸、勇于拥抱欣喜。要相信,无论你现在多么不幸,永远都不是最不堪的那一个!
Python 的高级语言特性一直是我们学习 Python 的一个难点,大部分人并没有做到熟练的掌握,甚至去学习它都感觉很困难,「生成器」作为其中甚是有用的特性之一,更是如此。
掌握生成器中的惰性求值^;
本章小结:
- lambda表达式(单行函数):不推荐直接赋给变量使用;一般是在高阶函数中使用;不允许出现赋值表达式=;
- 生成器,则是在迭代器的基础上(可以用for循环,可以使用next()),再实现了yield。带yield的 f 函数, f()为生成器对象;add 为 函数, add() 也为对象;
- 生成器函数中,执行一次后会暂停,将yield表达式的值返回;return 语句依然可以终止函数运行,但return语句的返回值不能被获取到,导致无法继续获取下一个值,抛出StopIteration 异常;
如果函数没有显示的 return 语句,生成器函数执行到结尾,一样会抛出StopIteration 异常;- 生成器 yield() 语句非常重要,再次重申;yield语句会暂停函数执行;让出控制权;
- 迭代器,是在可迭代的基础上,加了一个next()方法。
- 协程概念来源:有2个生成器A\B;执行一下a,再执行一下b,交替进行;或者执行2下a,执行一下b;
- 协程的效率非常非常高;远远超过多线程;
- 每个线程里面的 栈值 是有极限的,
- yield from: for x in range(1000): # yield x for x in range(1000): = yield from range(1000)
Python并发编程之从生成器使用入门协程
1. 匿名函数
lambda 函数也叫匿名函数,及即没有具体名称的函数,它允许快速定义单行函数,类似于 C 语言的宏,可以用在任何需要函数的地方
lambda 与 def 对比
区别 | def | lambda |
---|---|---|
名称 | 有 | 无 |
返回值 | 函数对象但不给标识符 | 任意 |
作用 | 简单的 | 简单/复杂 |
作用范围 | 不能共享 | 可共享 |
相比常规函数,lambda 函数的语法会简单很多:
lambda argument(s): expression
key = lambda x: str(x) # 在函数体中,等于 key = str
匿名函数的特点
- 只能有一个表达式
- 不用写return,返回值就是该表达式的结果
- 因为匿名函数没有名字,不必担心函数名冲突
- lambda 中不能出现赋值表达式;
(lambda x : x = x + 1) | SyntaxError: invalid syntax
此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:f=lambda x:x+2,我们把表达式存储到f中,调用匿名函数就可以写成如下表达式:
2. 高阶函数
指的是python中内置的可以把函数作为参数传入的函数
[x for x in (lambda *args:map(lambda x: x+1,args))(*range(5))]
[1, 2, 3, 4, 5]
[x for x in (lambda *args:map(lambda x: (x+1,args),args))(*range(5))]
[(1, (0, 1, 2, 3, 4)),
(2, (0, 1, 2, 3, 4)),
(3, (0, 1, 2, 3, 4)),
(4, (0, 1, 2, 3, 4)),
(5, (0, 1, 2, 3, 4))]
(1) sorted()
sorted(iterable,key,reverse) 其中key就是一个函数参数
sorted(list1,key=函数)
通过key这个函数指定规则
系统的排序函数
(2) map()
map(function,iterable)
对iterable中的item依次执行function(item),执行结果输出为list
map函数返回的是一个map对象,需要将其转化为列表输出
list(map(str,range(1,6)))
['1', '2', '3', '4', '5']
(3) redece()
reduce(function,iterable)
参数function是函数,此函数的参数必须两个eg:lambda x,y:x*y
(4) filter()
filter过滤: 对iterable中的item依次执行function(item),将执行结果为True(!=0)的item组成一个List/String/Tuple(取决于iterable的类型)返回,False则退出(0),进行过滤;
3.生成器 generator、可迭代、迭代器
生成器: 指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象;
生成器函数:
1.函数体中包含yield语句的函数,返回生成器对象;
2.生成器对象,是一个可迭代对象,是一个迭代器;
3.生成器对象,是延迟计算、惰性求值;
4.生成器对象只求值一次;
迭代器,是在可迭代的基础上,加了一个next()方法。
生成器,则是在迭代器的基础上(可以用for循环,可以使用next()),再实现了yield。
3.1 generator —— 生成器
>>> tuple1 = (x**x for x in range(3))
>>> tuple1
<generator object <genexpr> at 0x0000000001DF16D8>
print((lambda *args:(x for x in args))(*range(5)))
-------------------------------------------------------------
<generator object <lambda>.<locals>.<genexpr> at 0x00000227AB0E8318>
在 Python 中,定义生成器必须要使用yield 这个关键词,yield 翻译成中文有「生产」这方面的意思。在 Python 中,它作为一个关键词,是生成器的标志。(yield 拨一下转一下)
>>> def f():
... yield 0
... yield 1
... yield 2
...
>>> f
<function f at 0x00000000004EC1E0>
上面是写了一个很简单的 f 函数,代码块是 3 个 yield 发起的语句,下面让我们来看看如何使用它:
>>> fa = f()
>>> fa
<generator object f at 0x0000000001DF1660>
>>> type(fa)
<class 'generator'>
上述操作可以看出,我们调用函数得到了一个生成器(generator)对象。
>>> dir(fa)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__',
'__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
看到了 iter() 和 next(),虽然我们在函数体内没有显示的写 iter() 和 next(),仅仅是写了 yield,但它就已经是「迭代器」了。
必须 先生成 生成器对象;
>>> fa = f() #引用生成器对象,生成器函数才能生效;
>>> fa.__next__() #生成器开始执行,遇到了第一个 yield,然后返回后面的 0,并且挂起(即暂停执行)
0
>>> fa.__next__() #从上次暂停的位置开始,继续向下执行,遇到第二个 yield,返回后面的值 1,再挂起。
1
>>> fa.__next__() # next(fa)
2
>>> fa.__next__() #从上次暂停的位置开始,继续向下执行,但是后面已经没有 yield 了,所以 __next__() 发生异常。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
含有 yield 关键词的函数 f() 是一个生成器对象,这个生成器对象也是迭代器。所以就有了这样的定义:把含有 yield 语句的函数称为生成器,生成器是一种用普通函数语法定义的迭代器。
3.2 迭代器
迭代器,是在可迭代的基础上实现的。要创建一个迭代器,我们首先,得有一个可迭代对象;
对比可迭代对象,迭代器其实就只是多了一个函数而已。就是next(),我们可以不再使用for循环来间断获取元素值。而可以直接使用next()方法来实现。
from collections.abc import Iterator
aStr = 'abcd' # 创建字符串,它是可迭代对象
aIterator = iter(aStr) # 通过iter(),将可迭代对象转换为一个迭代器
print(isinstance(aIterator, Iterator)) # True
next(aIterator) # a
next(aIterator) # b
next(aIterator) # c
next(aIterator) # d
扩展知识:
迭代器,是其内部实现了,next 这个魔术方法。(Python3.x,Python2中是next()方法)
可以通过,dir()方法来查看是否有next来判断一个变量是否是迭代器的。
练习1: fib序列 生成器版本;
# fib生成器版本
def fib(n):
a,b = 0,1
for i in range(n):
a,b = b,a+b
yield a
for x in fib(3):
print(x)
-------------------------------
1
1
2
运行/激活生成器
激活主要有两个方法
- 使用 next()
- 使用 generator.send(None)
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(4)
# 通过交替执行,来说明这两种方法是等价的。
print(gen.send(None))
print(next(gen))
print(gen.send(None))
print(next(gen))
-------------------------------------------
0
1
2
3
生成器的执行状态
生成器在其生命周期中,会有如下四个状态
GEN_CREATED # 等待开始执行
GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态)
GEN_SUSPENDED # 在yield表达式处暂停
GEN_CLOSED # 执行结束
from inspect import getgeneratorstate
def mygen(n):
now = 0
while now < n:
yield now
now += 1
if __name__ == '__main__':
gen = mygen(2)
print(getgeneratorstate(gen))
print(next(gen))
print(getgeneratorstate(gen))
print(next(gen))
gen.close() # 手动关闭/结束生成器
print(getgeneratorstate(gen))
-------------------------------------------------------------
GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED
生成器函数嵌套:
def inc():
def counter():
i = 0
while True: #死循环;突破
i = i + 1 #计数
yield i
c = counter()
def _inc(): # return lambda : next(c)
return next(c)
return _inc
g = inc()
print(g())
print(g())
print(g())
----------------------------------
1
2
3
从生成器过渡到协程:yield
协程的效率非常高
有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None))。这种向暂停的生成器发送信息的功能通过 PEP 342 进入 Python 2.5 中,并催生了 Python 中协程的诞生
协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。
注意从本质上而言,协程并不属于语言中的概念,而是编程模型上的概念。
协程和线程,有相似点,多个协程之间和线程一样,只会交叉串行执行;也有不同点,
线程之间要频繁进行切换,加锁,解锁,从复杂度和效率来看,和协程相比,这确是一个痛点。
协程 通过使用 yield 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行。
协程概念来源:
def g1():
for i in range(5):
yield i
def g2():
for i in 'abcde':
yield i
gen1 = g1()
gen2 = g2()
for i in range(5): # 协程的理念:交替打印函数1,2
print(next(gen1))
print(next(gen2))
-----------------------------------
0
a
1
b
2
c
3
d
4
e
协程 调度器 实现策略 总结:
1. 有2个生成器A\B;执行一下a,再执行一下b,交替进行;或者执行2下a,执行一下b;
2. 可以引入调度的策略实现切换的方式;
新语法 —— yield from
在 Python 3.3 中新增了 yieldfrom 语法,这是全新的语言结构,是 yield 的升级版。相比 yield ,该语法有两大优势,我们来举例说明它的用法。
1.yield from 语句可以替代 for 循环,避免了嵌套循环。
2.同 yield 一样,yield from 语句也只能出现在函数体内部,有 yield from 语句的函数叫做协程函数或生成器函数。
1 yield from 相当于 for循环 + yield
def inc():
for x in range(1000): # yield x for x in range(1000): = yield from range(1000)
yield x
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
-----------------------------
0
1
2
def inc():
# for x in range(1000): # yield x for x in range(1000): = yield from range(1000)
yield from range(1000)
foo = inc()
print(next(foo))
print(next(foo))
----------------------------
0
1
2
2 生成器、迭代器、yield from
def counter(n): # 生成器
for x in range(n):
yield x
def inc(n):
yield from counter(n)
foo = inc(10) # 生成一个生成器对象
print(next(foo)) #迭代器
---------------------------------------------------
0