函数 (part 3)
本节主要讲函数的 lambda 表达式, 生成器
lambda 表达式 (匿名函数)(P48)
匿名函数(lambda 表达式)语法规则:lambda arg1, arg2, arg3, ... argN : expression
。
其中 arg
为参数,expression
为函数实现的表达式以及返回值
我们把lambda表达式作为极致精简的函数,相当于传统函数定义的:def <lambda> (arg1,arg2,arg3,... argN): return expression
示例 1:
# 传统定义的函数 def squareX(x): return x*x squareX(3)
9
# lambda 表达式 squareY = lambda y : y*y squareY(3)
9
# 传统定义的函数函数名就是一个函数的引用 squareX
<function __main__.squareX(x)>
# 而 lambda 表达式,整个表达式就是一个函数的引用 squareY
<function __main__.<lambda>(y)>
两者重大的区别就是,lambda 是一个表达式,可以用在常规函数不可能存在的地方。
示例2:lambda 表达放入列表(这里只是做示例示范可以,但不推荐大家开发的时候这么写哈)
y = [lambda x:x*x,2,3] y[0](y[1]) # 把 y 列表的第二个值(2),传入列表第一个值 lambda 表达式中
4
lambda 表达式与 map
,filter
等函数合用。这两者的参数都是要求传入一个用于计算的函数,此时就可以使用 lambda 表达式。
比如 map
函数,第一个参数就是传入一个函数的引用,第二个参数要求是序列类型,其会把每个元素挨个传递给第一个参数指定的函数,在运算之后把结果构成一个迭代器返回。
示例3:
mapped = map(lambda x:ord(x)+10, 'abcdef') list(mapped)
[107, 108, 109, 110, 111, 112]
# 如果我们用传统的函数 def tradation(x): return ord(x)+10 list(map(tradation,'abcdef'))
[107, 108, 109, 110, 111, 112]
# 再比如我们用在 filter 上 list(filter(lambda x : x%2,range(10))) # 求 10 以内的所有奇数
[1, 3, 5, 7, 9]
总结,lambda 是一个表达式而非语句,所以它能够出现在 Python 语法不允许 def 语句出现的地方(优势)。但由于所有的功能代码都局限在一个表达式中实现,因此 lambda 通常也只能实现那些较为简单的需求。一般来讲,匿名函数用于实现简单的需求,def 语句负责用于定义功能复杂的函数,去处理复杂的工作。
生成器 (generator) (P49)
对于调用一个普通的 Python 函数来说,一般我们都是从函数的第一行代码开始执行的,当所有语句执行完毕,或者遇到 return 语句的时候,这个函数就算是结束了。一旦函数将控制权交还给调用者的时候,这就意味这这个函数的结束。函数中做的所有工作,以及保存在局部变量中的数据也都会丢失。当再次调用的时候,一切从头开始。
有没有办法让函数在退出之后还能保留状态呢?使用闭包或者全局变量。不过过多使用全局变量会污染命名空间,闭包的定义又相对复杂。
此时,生成器 (generator) 就是一个更简单和安全的方法。
定义一个生成器:
在函数中使用 yield
表达式来代替 return
语句
示例1:
def counter(): i=0 while i <= 3: yield i i+=1 # 这样我们就定义了一个 叫 counter 的生成器 # 现在我们调用 counter() 函数 counter() # 可以看到得到的不再是一个返回值,而是一个生成器对象 `generator object`
<generator object counter at 0x0000017F4A3B8430>
# counter 现在是一个生成器了,我们可以在 for 中使用 for i in counter(): print(i)
0 1 2 3
我们知道 for 语句,是从一个可迭代对象里面每次获取一个数据。这个 counter 生成器的作用就是每次调用的时候提供一个数据,注意是提供一个数据。
更深入讲,每次在执行到 yield i
的时候,就生成一个数据,暂停并保留状态。下一次调用则从下一个语句 i+=1
开始继续执行。
注意:生成器它不像是列表,元组之类的可迭代对象,生成器可以看作是一个制作机器,他的作用就是每调用一次提供一个数据,并且会记住当时的状态。而列表元组这些可迭代对象则是容器,他们里面存放的是早已经准备好的所有数据。生成器是一种特殊的迭代器,他首先不走回头路,其次支持 next()
函数
c = counter() next(c)
0
next(c)
1
next(c)
2
next(c) # 如果再 使用 next(c) 就会抛出 StopIteration 的异常
3
由于生成器每调用一次获取一个结果的特性,所以生成器对象是无法使用下标索引这种随机访问的方式的
c = counter() print(next(c)) print(next(c)) # 此时 c 的状态 i =1 print('_'*30) for i in c: # 再调用是从 i=1开始 i=i+1 print(i)
0 1 ______________________________ 2 3
示例2:
使用生成器来实现 斐波那契数列
斐波那契数列:由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出:0,1,1,2,3,5,8 …
def fib(): back1,back2 = 0,1 while True: yield back1 back1,back2 = back2,back1+back2 f = fib() print(next(f)) print(next(f)) print(next(f)) print(next(f)) print(next(f)) print(next(f)) print(next(f)) print(next(f))
0 1 1 2 3 5 8 13
这里我们写了一个无限的斐波那契数列,如果我们不小心用到了 for 上,他就会一直生成下去。此时要用 control+C 强制退出,否则会一直进行下去
生成器表达式 (generator expression)
在之前的课程中我们提到过,列表有列表推导是而元组则没有。这里就可以做一个解答,列表推导式的 []
换成 ()
,其实就变成了生成器表达式。
示例:
(i**2 for i in range(3))
<generator object <genexpr> at 0x0000017F4A3C49E0>
t = (i**2 for i in range(3)) # range(3) 包括 0,1,2 不包括 3 print(next(t)) print(next(t)) print(next(t))
0 1 4
这种利用推导的形式获得生成器的方法,我们称之为生成器表达式。生成器表达式和列表推导式最大的不同就是,列表推导式会一下子将所有数据生产出来并放到一个列表中,但是生成器表达式一次只生成一个数据。
总结来说,生成生成器的两种方法一个就是使用 yield
表达式,另一个就是直接使用生成器表达式。
附言: 题目:Self-study Python Fish-C Note-13 P48-P49 本文为自学B站上鱼C的python课程随手做的笔记。一些概念和例子我个人为更好的理解做了些查询和补充 因本人水平有限,如有任何问题,欢迎大家批评指正! 原视频链接:https://www.bilibili.com/video/BV1c4411e77t?p=8