4
函数
Python 里函数太重要了 (说的好像在别的语言中函数不重要似的)。函数的通用好处就不用多说了吧,重复使用代码,增强代码可读性等等。
还记得 Python 里面『万物皆对象』么?Python 把函数也当成对象,可以从另一个函数中返回出来而去构建高阶函数,比如
4.1
正规函数
Python 里面的正规函数 (normal function) 就像其他语言的函数一样,之所以说正规函数是因为还有些「不正规」的,比如匿名函数,高阶函数等等。
但即便是正规函数,Python 的函数具有非常灵活多样的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。从简到繁的参数形态如下:
每种参数形态都有自己对应的应用,接下来用定义一个金融产品为例来说明各种参数形态的具体用法。
先从最简单的「位置参数」开始介绍:
位置参数
解释一下函数里面的各个部分:
用金融产品举例,每个产品都有自己的 ID,定义 instrument 函数,它只有一个「位置参数」。
def instrument( id ):
print( 'id:', id )
给 id 赋值 'MM1001' 并运行该函数,得到
instrument( 'MM1001' )
id: MM1001
当然「位置参数」可以是多个,比如 id 和 ntl (代表 notional,本金,例如债券的本金是一亿,期权的本金是一百万等等):
def instrument1( id, ntl ):
print( 'id:', id )
print( 'notional:', ntl )
给 ntl 赋值 100000 并运行该函数,得到
instrument1( 'MM1001', 1000000 )
id: MM1001
notional: 1000000
如果你没有给 ntl 赋值,程序会报错
instrument1( 'MM1001' )
TypeError: instrument1() missing 1
required positional argument: 'ntl'
怎么破?来看看「默认参数」。
默认参数
解释一下函数里面的各个部分 (黄色高亮的是新内容):
在对金融产品估值时,通常对一个单位的产品先估值,再乘以产品具体的本金。比如 1 美元的债券现值为 0.98 美元,那么 1 亿美元的债券现值为 98,000,000 美元。
因此我们将 ntl 设为「默认参数」,并设定一个默认值 1。
def instrument2( id, ntl=1 ):
print( 'id:', id )
print( 'notional:', ntl )
这时调用 instrument2 不给 ntl 赋值也可以运行,ntl 就取其默认值 1。
instrument2( 'MM1001' )
id: MM1001
notional: 1
当然你可以把 ntl 像「位置参数」那样对待,给它设定任何值
instrument2( 'MM1001', 100 )
id: MM1001
notional: 100
使用「默认参数」最大的好处是能降低调用函数的难度,不过有个知识点需要注意。
知识点
默认函数一定要放在位置参数后面,不然程序会报错。
def instrument2( ntl=1, id ):
print( 'id:', id )
print( 'notional:', ntl )
SyntaxError: non-default argument
follows default argument
当然「默认参数」可以是多个,比如 ntl 和 curR (报表货币,对于中国的银行用 CNY)
def instrument3( id, ntl=1, curR='CNY' ):
print( 'id:', id )
print( 'notional:', ntl )
print( 'reporting currency:', curR)
假设我们先给 ntl 和 curR 赋值 100 和 'USD'
instrument3( 'MM1001', 100, 'USD' )
id: MM1001
notional: 100
reporting currency: USD
但有时在调用函数时,我们会记不住参数的顺序。比如 ntl 和 curR 的位置写反了
instrument3( 'MM1001', 'USD', 100 )
id: MM1001
notional: USD
reporting currency: 100
得到的结果毫无意义,那么记不住参数顺序怎么破?在调用参数把它的「关键字」也带上,我们就可以随便调换参数的顺序。
instrument3( 'MM1001', curR='USD', ntl=100 )
id: MM1001
notional: 100
reporting currency: USD
这样怎么改变参数顺序都可以打印出有意义的结果了。读者可能会说了我也记不住「关键字」啊,是的,但是「关键字」看上去有具体的意义,绝对比你记住参数顺序容易多了吧。
可变参数
在 Python 函数中,还可以定义「可变参数」。顾名思义,可变参数就是传入的参数个数是可变的,可以是 0, 1, 2 到任意个。
解释一下函数里面的各个部分 (黄色高亮的是新内容):
金融产品未来多个折现现金流 (discounted cash flow, DCF),但不知道具体多少个,这时我们可以用 *args 来表示不确定个数的 DCF。下面程序也对 DCF 加总得到产品现值 (present value, PV)
def instrument4( id, ntl=1, curR='CNY', *args ):
PV = 0
for n in args:
PV = PV + n
print( 'id:', id )
print( 'notional:', ntl )
print( 'reporting currency:', curR )
print( 'present value:', PV*ntl )
如果一个产品 (单位本金) 在后 3 年的折现现金流为 1, 2, 3,将它们传入 *args,计算出它的现值为 600 = 100*(1+2+3)。
instrument4( 'MM1001', 100, 'EUR', 1, 2, 3 )
id: MM1001
notional: 100
reporting currency: EUR
present value: 600
除了直接传入多个参数之外,还可以将所有参数先组装成元组 DCF,用以「*DCF」的形式传入函数 (DCF 是个元组,前面加个通配符 * 是拆散元组,把元组的元素传入函数中)
DCF = (1, 2, 3, 4, 5)
instrument4( 'MM1001', 10, 'EUR', *DCF )
id: MM1001
notional: 10
reporting currency: EUR
present value: 150
可变参数用两种方式传入
关键字参数
解释一下函数里面的各个部分 (黄色高亮的是新内容):
「可变参数」和「关键字参数」的同异总结如下:
在定义金融产品,有可能不断增加新的信息,比如交易对手、工作日惯例、工作日计数惯例等等。我们可以用「关键字参数」来满足这种需求,即用 **kw。
def instrument5( id, ntl=1, curR='CNY', *args, **kw ):
PV = 0
for n in args:
PV = PV + n
print( 'id:', id )
print( 'notional:', ntl )
print( 'reporting currency:', curR )
print( 'present value:', PV*ntl )
print( 'keyword:', kw)
如果不传入任何「关键字参数」,kw 为空集。
instrument5( 'MM1001', 100, 'EUR', 1, 2, 3 )
id: MM1001
notional: 100
reporting currency: EUR
present value: 600
keyword: {}
当知道交易对手 (counterparty) 是高盛时,给函数传入一个「关键字参数」,ctp = 'GS'。
instrument5( 'MM1001', 100, 'EUR', 1, 2, 3, ctp='GS' )
id: MM1001
notional: 100
reporting currency: EUR
present value: 600
keyword: {'ctp': 'GS'}
当知道日期计数 (daycount) 是 act/365 时,再给函数传入一个「关键字参数」,dc = 'act/365'。
instrument5( 'MM1001', 100, 'EUR', 1, 2, 3,
dc='act/365', ctp='GS' )
id: MM1001
notional: 100
reporting currency: EUR
present value: 600
keyword: {'dc': 'act/365', 'ctp': 'GS'}
除了直接传入多个参数之外,还可以将所有参数先组装成字典 Conv,用以「**Conv」的形式传入函数 (Conv 是个字典,前面加个通配符 ** 是拆散字典,把字典的键值对传入函数中)
DCF = (1, 2, 3, 4, 5)
Conv = {'dc':'act/365', 'bdc':'following'}
instrument5( 'MM1001', 10, 'EUR', *DCF, **Conv )
id: MM1001
notional: 10
reporting currency: EUR
present value: 150
keyword: {'dc': 'act/365', 'bdc': 'following'}
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。
解释一下函数里面的各个部分 (黄色高亮的是新内容):
如果要限制关键字参数的名字,就可以用「命名关键字参数」,例如,用户希望交易对手 ctp 是个关键字参数。这种方式定义的函数如下:
def instrument6( id, ntl=1, curR='CNY', *, ctp, **kw ):
print( 'id:', id )
print( 'notional:', ntl )
print( 'reporting currency:', curR )
print( 'counterparty:', ctp )
print( 'keyword:', kw)
从调用函数 instrument6 得到的结果可看出 ctp 是「命名关键字参数」,而 dc 是「关键字参数」。
instrument6( 'MM1001', 100, 'EUR',
dc='act/365', ctp='GS' )
id: MM1001
notional: 100
reporting currency: EUR
counterparty: GS
keyword: {'dc': 'act/365'}
使用命名关键字参数时,要特别注意不能缺少参数名。下例没有写参数名 ctp,因此 'GS' 被当成「位置参数」,而原函数只有 3 个位置函数,现在调用了 4 个,因此程序会报错:
instrument6( 'MM1001', 100, 'EUR',
'GS', dc='act/365' )
TypeError: instrument6() takes from 1 to 3
positional arguments but 4 were given
参数组合
在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:
要注意定义可变参数和关键字参数的语法:
命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名关键字参数不要忘了写分隔符 *,否则定义的是位置参数。
警告:虽然可以组合多达 5 种参数,但不要同时使用太多的组合,否则函数很难懂。
4.2
匿名函数
在 Python 里有两种函数
匿名函数 (anonymous function) 的说明如下:
解释一下函数里面的各个部分:
注意 lambda 函数没有所谓的函数名 (function_header),这也是它为什么叫匿名函数。下面是一些 lambda 函数示例:
lambda x, y: x*y;函数输入是 x 和 y,输出是它们的积 x*y
func = lambda x, y: x*y
func(2, 3)
6
lambda *args: sum(args);输入是任意个数的参数,输出是它们的和
func = lambda *args: sum(args)
func( 1, 2, 3, 4, 5 )
15
lambda **kwargs: 1;输入是任意键值对参数,输出是 1
func = lambda **kwargs: 1
func( name='Steven', age='36' )
1
匿名函数 Vs 正规函数
看个具体的平方函数例子:
lbd_sqr = lambda x: x ** 2
lbd_sqr
<function __main__.<lambda>(x)>
这个 lambda 函数 lbd_sqr 做的事和下面正规函数 sqr 做的一样:
def sqr(x):
return x ** 2
sqr
<function __main__.sqr(x)>
用实际结果来验证一下:
print( sqr(9) )
print( lbd_sqr(9) )
81
81
对于 lambda 函数,有时我们会过用 (overuse) 它或误用 (misuse) 它。
Misuse
误用情况:如果用 lambda 函数只是为了赋值给一个变量,用 def 的正规函数。
上面举的例子就是反例,
lbd_sqr = lambda x: x ** 2
def sqr(x): return x ** 2
print( lbd_sqr )
print( sqr )
<function <lambda> at 0x000001997AA721E0>
<function sqr at 0x000001997AA72268>
你看,lbd_sqr 的返回值是以 <lambda> 标识的函数,而 sqr 的返回时是以 sqr 为标识的函数,明显后者一看就知道该函数是「计算平方」用的。
Overuse
过用情况:如果一个函数很重要,它需要一个正规的名字。
有些人觉得 lambda 函数很酷,会不分场合疯狂地用它。比如下面一个例子,根据「字符长度」和「首个字母」来对列表来排序。
colors = ["Goldenrod", "purple", "Salmon", "Cyan"]
sorted(colors, key=lambda c: (len(c), c.casefold()))
['Cyan', 'purple', 'Salmon', 'Goldenrod']
在 sorted 函数有个 key 的参数,key 的值是排序的根据。比如上面用 lambda 函数设定字符长度 len(c) 和忽略大小的首个字母 c.casefold(),c 表示具体的列表。
坦白的说,这样用 lambda 函数看起来是很酷,但是增加了使用者的「思考成本」,用 def 显性定义个函数可读性会好很多。
def length_and_alphabetical(str):
"""Return sort key: length first, then caseless string."""
return (len(str), str.casefold())
sorted(colors, key=length_and_alphabetical)
['Cyan', 'purple', 'Salmon', 'Goldenrod']
用正规函数还能加个函数说明 (docstring),再起个描述性强的函数名,让人一看就知道该函数做什么。
4.3
高阶函数
高阶函数 (high-order function) 在函数化编程 (functional programming) 很常见,主要有两种形式:
Map, Filter, Reduce
Python 里面的 map, filter 和 reduce 属于第一种高阶函数,参数是函数。这时候是不是很自然的就想起了 lambda 函数?
作为内嵌在别的函数里的参数,lambda 函数就像微信小程序一样,即用即丢,非常轻便。
首先看看 map, filter 和 reduce 的语法:
看个具体的平方示例,用 map 函数对列表每个元素平方。
lst = [1, 2, 3, 4, 5]
map_iter = map( lambda x: x**2, lst )
print( map_iter )
print( list(map_iter) )
<map object at 0x0000018C83E72390>
[1, 4, 9, 16, 25]
在 map 函数中
注意 map_iter 是 map 函数的返回对象 (它是一个迭代器),想要将其内容显示出来,需要用 list 将其转换成「列表」形式。有点奇怪是不是?为什么 map 函数不直接返回列表呢?看完下面「惰性求值」的知识点就明白了。
知识点
惰性求值 (lazy evaluation) 也称为传需求调用 (call-by-need),目的是最小化计算机要做的工作。
在上例中,map 函数作用到列表,并不会立即进行求平方,而是当你用到其中某些元素时才去求平方。惰性是指,你不主动去遍历它,就不会计算其中元素的值。
为什么要有 「惰性求值」呢?在本例看起来毫无必要,但试想大规模数据时,一次性处理往往抵消而且不方便,而惰性求值解决了这个问题,它把计算的具体步骤延迟到了要实际用该数据的时候。
惰性序列可以看作是一个流 (flow),需要的时候从其中取一滴水。想想 tensorflow 里构建的流图?
接着再看看 filter 函数,顾名思义就是筛选函数,那么我们把刚才列表中的计数筛选出来吧。
filter_iter = filter(lambda n: n % 2 == 1, lst)
print( filter_iter )
print( list(filter_iter) )
<filter object at 0x0000018C83E722E8>
[1, 3, 5]
在 filter 函数中
同样,filter_iter 作为 filter 函数的返回对象,也是一个迭代器,想要将其内容显示出来,需要用 list 将其转换成「列表」形式。
最后来看看 reduce 函数,顾名思义就是累积函数,把一组数减少 (reduce) 到一个数。
from functools import reduce
reduce( lambda x,y: x+y, lst )
15
在 reduce 函数中
在 reduce 函数的第三个参数还可以赋予一个初始值,
reduce( lambda x,y: x+y, lst, 100 )
115
这是累积从 100 和列表 lst = [1,2,3,4,5] 的第一个元素 1 开始,一直加到整个 lst 元素遍历完,因此最后求和为 115。
小结一下,对于 map, filter 和 reduce,好消息是,Python 支持这些基本的操作;而坏消息是,Python 不建议你使用它们。下节的「解析式」可以优雅的替代 map 和 filter。
除了 Python 这些内置函数,我们也可以自己定义高阶函数,如下:
def apply_to_list( fun, some_list ):
return fun(some_list)
这个 apply_to_list 函数和上面的 map, filter 和 reduce 的格式类型,第一个参数 fun 是可以作用到列表的函数,第二个参数是一个列表。下面代码分别求出列表中所有元素的和、个数和均值。
lst = [1, 2, 3, 4, 5]
print( apply_to_list( sum, lst ) )
print( apply_to_list( len, lst ) )
print( apply_to_list( lambda x:sum(x)/len(x), lst ) )
15
5
3.0
闭包
Python 里面的闭包 (closure) 属于第二种高阶函数,返回值是函数。下面是一个闭包函数。
def make_counter(init):
counter = [init]
def inc(): counter[0] += 1
def dec(): counter[0] -= 1
def get(): return counter[0]
def reset(): counter[0] = init
return inc, dec, get, reset
此函数的作用是做一个计数器,可以
inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
get()
3
续了三秒。
dec()
get()
2
减了一秒,相当于续了两秒。
reset()
get()
0
重新计秒。
属于第二类 (返回值是函数) 的高阶函数还有「偏函数」和「柯里化」,由于它们比较特别,因此专门分两节来讲解。
4.4
偏函数
偏函数 (paritial function) 主要是把一个函数的参数 (一个或多个) 固定下来,用于专门的应用上 (specialized application)。要用偏函数用从 functools 中导入 partial 包:
from functools import partial
举个排序列表里元素的例子
lst = [3, 1, 2, 5, 4]
sorted( lst )
[1, 2, 3, 4, 5]
我们知道 sort 函数默认是按升序排列,假设在你的应用中是按降序排列,你可以把函数里的 reverse 参数设置为 True。
sorted( lst, reverse=True )
[5, 4, 3, 2, 1]
这样每次设定参数很麻烦,你可以专门为「降序排列」的应用定义一个函数,比如叫 sorted_dec,用偏函数 partial 把内置的 sort 函数里的 reverse 固定住,代码如下:
sorted_dec = partial( sorted, reverse=True )
sorted_dec
functools.partial(<built-in function sorted>, reverse=True)
不难发现 sorted_dec 是一个函数,而且参数设置符合我们的应用,把该函数用到列表就能对于降序排列。
sorted_dec( lst )
[5, 4, 3, 2, 1]
小结,当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,即偏函数,它可以固定住原函数的部分参数,从而在调用时更简单。
4.5
柯里化
最简单的柯里化 (currying) 指的是将原来接收 2 个参数的函数 f(x, y) 变成新的接收 1 个参数的函数 g(x) 的过程,其中新函数 g = f(y)。
以普通的加法函数为例:
def add1(x, y):
return x + y
通过嵌套函数可以把函数 add1 转换成柯里化函数 add2。
def add2(x):
def add(y):
return x + y
return add
仔细看看函数 add1 和 add2 的参数 (常数用红色表示)
下面代码也证实了上述分析:
add1
add2
g = add2(2)
g
<function __main__.add1(x, y)>
<function __main__.add2(x)>
<function __main__.add2.<locals>.add(y)>
比较「普通函数 add1」和「柯里化函数 add2」的调用,结果都一样。
print( add1(2, 3) )
print( add2(2)(3) )
print( g(3) )
5
5
5
5
解析式
5.1
大框架
解析式 (comprehension) 是将一个可迭代对象转换成另一个可迭代对象的工具。
上面出现了两个可迭代对象 (iterable),不严谨地说,容器类型数据 (str, tuple, list, dict, set) 都是可迭代对象。
下面写出列表、字典和集合解析式的伪代码 (pseudo code)。
# list comprehension
[值 for 元素 in 可迭代对象 if 条件]
# dict comprehension
{键值对 for 元素 in 可迭代对象 if 条件}
# set comprehension
{值 for 元素 in 可迭代对象 if 条件}
不难发现,这些解析式都有
对,解析式就是为了把「带条件的 for 循环」简化成一行代码的。
也不难发现,列表解析式整个语句用「中括号 []」框住,而字典和集合解析式整个语句中「大括号 {}」框住。想想 list, dict 和 set 用什么括号定义就明白了。
通过这两个发现,我们大概对解析式有个一些直观但还比较模糊的理解,根据 input-operation-output 这个过程总结:
有点抽象?我知道,下节用「列表解析式」来进一步举例说明。
5.2
列表解析式
列表解析式
问题:如何从一个含整数列表中把奇数 (odd number) 挑出来?
简单,用带 if 的 for 循环呗。
lst = [1, 2, 3, 4, 5]
odds = []
for n in lst:
if n % 2 == 1:
odds.append(n * 2)
odds
[1, 3, 5]
任务完成了,但这个代码有好几行呢,不简洁,看看下面这一行代码:
odds = [n * 2 for n in lst if n % 2 == 1]
odds
[1, 3, 5]
咋一看从「for 循环」到「解析式」不直观,我来用不同颜色把这个过程可视化一下,如下图:
你可以把「for 循环」到「解析式」的过程想像成一个「复制-粘贴」的过程:
现在清楚多了吧,在把上面具体的例子推广到一般的例子,从「for 循环」到「列表解析式」的过程如下:
因此现在你可以一口气写出「列表解析式」了吧,或者可以一口气读懂别人写的「列表解析式」了吧。下节我们会用几个实例来巩固下理解。
现在你可能会说上面「for 循环」只有一层,如果两层怎么转换「列表解析式」?具体来说怎么解决下面这个问题。
问题:如何用「列表解析式」将一个二维列表中的元素按行一个个展平?
没思路?先用「for 循环」试试?
flattened = []
for row in lst:
for n in row:
flattened.append(n)
套用一维「列表解析式」的做法
两点需要注意:
我承认我一开始也习惯写成下图错误的那种 (多练几次就可以改过来了),
其他解析式
我们把「列表解析式」那一套举一反三的用到其他解析式上,用下面两图理解一下「字典解析式」和「集合解析式」。
小结
再回顾下三种解析式,我们发现其实它们都可以实现上节提到的 filter 和 map 函数的功能,用专业计算机的语言说,解析式可以看成是 filter 和 map 函数的语法糖。
知识点
语法糖 (syntactic sugar):指计算机语言中添加的某种语法,对语言的功能没有影响,但是让程序员更方便地使用。
语法盐 (syntactic salt):指计算机语言中添加的某种语法,使得程序员更难写出坏的代码。
语法糖浆 (syntactic syrup):指计算机语言中添加的某种语法,没能让编程更加方便。
了解完概念,我们看看为什么说「列表解析式」是 「map/filter」的语法糖,两者的类比图如下:
首先发现两者都是把原列表根据某些条件转换成新列表,再者
为了达到相同目的,明显「列表解析式」是种更简洁的方式。
用「在列表中先找出奇数再乘以 2」的例子,对于列表 lst = [1, 2, 3, 4, 5],我们先看「列表解析式」的实现:
[ n*2 for n in lst if n%2 == 1]
[2, 6, 10]
再看「map/filter」的实现:
list( map(lambda n: n*2, filter(lambda n: n%2 == 1, lst)) )
[2, 6, 10]
谁简谁繁,一目了然。
5.3
小例子
简单例子
问题:用解析式将二维元组里每个元素提取出来并存储到一个列表中。
tup = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
先遍历第一层元组,用 for t in tup,然后遍历第二层元组,用 for x in t,提取每个 x 并"放在“列表中,用 []。代码如下:
flattened = [x for t in tup for x in t]
flattened
[1, 2, 3, 4, 5, 6, 7, 8, 9]
至于为什么按 for t in tup for x in t 这个顺序写,还记得上节的这张图吗?
如果我们想把上面「二维元组」转换成「二维列表」呢?
[ [x for x in t] for t in tup ]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
复杂例子
回到上篇引言的问题。
问题:用解析式把以下这个不规则的列表 a 打平 (flatten)?
a = [1, 2, [3, 4], [[5, 6], [7, 8]]]
用解析式一步到位解决上面问题有点难,特别是列表 a 不规则,每个元素还可以是 n 层列表,因此我们需要递推函数 (recursive function),即一个函数里面又调用自己。
def f(x):
if type(x) is list:
return [y for l in x for y in f(l)]
else:
return [x]
f(a)
[1, 2, 3, 4, 5, 6, 7, 8]
整个列表遍历一遍,有四个元素,1, 2, [3, 4] 和 [[5, 6], [7, 8]]。函数 f(x) 是一个递推函数,当 x 是元素,返回 [x],那么
当 x 是列表,返回 [y for l in x for y in f(l)],当 x = [3 ,4] 时
整个 f([3 ,4]) 的返回值是 [3 ,4]。同理,当 x = [[5, 6], [7, 8]] 时,f(x) 的返回值是 [5, 6, 7, 8]。
到此,列表中四个元素 1, 2, [3, 4] 和 [[5, 6], [7, 8]] 的情况都分析完毕了,现在当 x = [1, 2, [3, 4], [[5, 6], [7, 8]]],f(x) 也运行到下面这步
return [y for l in x for y in f(l)]
那么
把这所有的 y 再合成一个列表不就是
[1, 2, 3, 4, 5, 6, 7, 8]
正规 (递推) 函数写好了,把它写成匿名函数也很简单了。
a = [1, 2, [3, 4], [[5, 6], [7, 8]]]
f = lambda x: [y for l in x for y in f(l)]
if type(y) is list else [x]
f(a)
[1, 2, 3, 4, 5, 6, 7, 8]
配着下图再理解一遍:
6
总结
本帖讨论了函数和解析式。优雅清晰是 python 的核心价值观,高阶函数和解析式都符合这个价值观。
函数包括正规函数 (用 def) 和匿名函数 (用 lambda),函数的参数形态也多种多样,有位置参数、默认参数、可变参数、关键字参数、命名关键字参数。匿名函数主要用在高阶函数中,高阶函数的参数可以是函数 (Python 里面内置 map/filter/reduce 函数),返回值也可以是参数 (闭包、偏函数、柯里化函数)。
解析式并没有解决新的问题,只是以一种更加简洁,可读性更高的方式解决老的问题。解析式可以把「带 if 条件的 for 循环」用一行程序表达出来,也可以实现 map 加 filter 的功能。
The End