4 函数
4.1 正规函数
Python 的函数具有非常灵活多样的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。从简到繁的参数形态如下:
- 1.位置参数 (positional argument)
- 2.默认参数 (default argument)
- 3.可变参数 (variable argument)
- 4.关键字参数 (keyword argument)
- 5.命名关键字参数 (name keyword argument)
- 6.参数组合
位置参数
- def - 定义正规函数要写 def 关键词。
- function_name - 函数名,起名最好有意义。
- arg1 - 位置参数 ,这些参数在调用函数 (call function) 时位置要固定。
- :- 冒号,在第一行最后要加个冒号。
- “”“docstring”"" - 函数说明,给使用函数这介绍该它是做什么的。
- statement - 函数内容。
用金融产品举例,每个产品都有自己的 ID,定义 instrument 函数,它只有一个「位置参数」。
def instrument( id ):
print( 'id:', id )
instrument( 'MM1001' )
id: MM1001
「位置参数」可以是多个,比如 id 和 ntl (代表 notional,本金,例如债券的本金是一亿,期权的本金是一百万等等)
# 「位置参数」可以是多个,比如 id 和 ntl
def instrument1( id, ntl ):
print( 'id:', id )
print( 'notional:', ntl )
instrument1( 'MM1001', 1000000 )
id: MM1001
notional: 1000000
位置参数特性:
- 这些参数在调用函数 (call function) 时位置要固定,顺序不能打乱;
- 任意一个位置参数没有赋值,程序会报错。
默认参数
- arg2 = v - 默认参数 = 默认值,调用函数的时候,默认参数已经有值,就可以不用再传值了。
在对金融产品估值时,通常对一个单位的产品先估值,再乘以产品具体的本金。比如 1 美元的债券现值为 0.98 美元,那么 1 亿美元的债券现值为 98,000,000 美元。
# 让ntl默认等于1亿,这样可以不用再给ntl赋值
def instrument2(id, ntl=100000000):
print( 'id:', id )
print( 'notional:', ntl )
instrument2('MM1001')
id: MM1001
notional: 100000000
instrument2('MM1001',10000)
id: MM1001
notional: 10000
默认参数特性:
- 默认函数一定要放在位置参数后面,不然程序会报错;
- 默认参数可以赋值,也可以不赋值
# 如果默认参数放前面,那么就会报错
def instrument2( ntl=1, id ):
print( 'id:', id )
print( 'notional:', ntl )
File "<ipython-input-34-057ffd7eae81>", line 2
def instrument2( ntl=1, id ):
^
SyntaxError: non-default argument follows default argument
# 默认参数可以有很多个
def instrument3( id, ntl=10000, curR='CNY' ):
print( 'id:', id )
print( 'notional:', ntl )
print( 'reporting currency:', curR)
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 到任意个。
- *args - 可变参数,可以是从零个到任意个,自动组装成元组。
金融产品未来多个折现现金流 (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', 100, 'EUR', *DCF )
id: MM1001
notional: 100
reporting currency: EUR
present value: 1500
可变参数特性:
- 可以直接传入,func(x, 1, 2, 3)
- 先组装列表或元组,再通过 *args 传入,func(x, *[1, 2, 3]) 或 func(x, *(1, 2, 3)),前面一定加个通配符 * 是拆散元组
关键字参数
- **kw - 关键字参数,可以是从零个到任意个,自动组装成字典。
「可变参数」和「关键字参数」的对比:
- 可变参数允许传入零个到任意个参数,它们在函数调用时自动组装为一个元组 (tuple)
- 关键字参数允许传入零个到任意个参数,它们在函数内部自动组装为一个字典 (dict)
在定义金融产品,有可能不断增加新的信息,比如交易对手、工作日惯例、工作日计数惯例等等。我们可以用「关键字参数」来满足这种需求,即用 **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=‘GS’;
- 也可以传入字典Conv = {‘dc’:‘act/365’, ‘bdc’:‘following’},再通过** Conv,拆散字典
命名关键字参数
- *, nkw - 命名关键字参数,用户想要输入的关键字参数,定义方式是在nkw 前面加个分隔符 *。
如果要限制关键字参数的名字,就可以用「命名关键字参数」,例如,用户希望交易对手 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( '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 Traceback (most recent call last)
<ipython-input-50-92dff9c9b5dd> in <module>
1 # 没有写参数名 ctp,因此 'GS' 被当成「位置参数」,而原函数只有 3 个位置函数,现在调用了 4 个,因此程序会报错:
----> 2 instrument6( 'MM1001', 100, 'EUR', 'GS', dc='act/365' )
TypeError: instrument6() takes from 1 to 3 positional arguments but 4 were given
参数组合
在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:
- 位置参数、默认参数、可变参数和关键字参数。
- 位置参数、默认参数、命名关键字参数和关键字参数。
要注意定义可变参数和关键字参数的语法:
- *args 是可变参数,args 接收的是一个 tuple
- **kw 是关键字参数,kw 接收的是一个 dict
命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名关键字参数不要忘了写分隔符" *,"否则定义的是位置参数。
4.2 匿名函数
在 Python 里有两种函数
- 用 def 关键词的正规函数
- 用 lambda 关键词的匿名函数
- lambda - 定义匿名函数的关键词。
- argument_list - 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。
- :- 冒号,在函数参数和表达式中间要加个冒号。
- expression - 函数表达式,输入函数参数,输出一些值。
lambda 函数没有所谓的函数名 (function_header),这也是它为什么叫匿名函数。
func = lambda x, y: x*y# 函数输入是 x 和 y,输出是它们的积 x*y
func(2, 3)
6
func = lambda *args: sum(args) # 输入是任意个数的参数,输出是它们的和
func( 1, 2, 3, 4, 5 )
15
func = lambda **kwargs: 1 # 输入是任意键值对参数,输出是 1
func( name='Steven', age='36' )
1
匿名函数与正规函数的区别
lbd_sqr = lambda x: x ** 2
lbd_sqr
<function __main__.<lambda>(x)>
def sqr(x):
return x ** 2
sqr
<function __main__.sqr(x)>
print(sqr(9))
print(lbd_sqr(9))
81
81
4.3 高阶函数
高阶函数 (high-order function) 在函数化编程 (functional programming) 很常见,主要有两种形式:
- 参数是函数 (map, filter, reduce)
- 返回值是函数 (closure, partial, currying)
map,filter,reduce
Python 里面的 map, filter 和 reduce 属于第一种高阶函数,参数是函数。
- map(函数 f, 序列 x):对序列 x 中每个元素依次执行函数 f,将 f(x) 组成一个「map 对象」返回 (可以将其转换成 list 或 set)
- filter(函数 f, 序列 x):对序列 x 中每个元素依次执行函数 f,将 f(x) 为 True 的结果组成一个「filter 对象」返回 (可以将其转换成 list 或 set)
- reduce(函数 f, 序列 x):对序列 x 的第一个和第二个元素执行函数 f,得到的结果和序列 x 的下一个元素执行函数 f,一直遍历完的序列 x 所有元素。
map
lst = [1, 2, 3, 4, 5]
map_iter = map( lambda x: x**2, lst ) # 返回结果是一个map对象
# 第一个参数是一个计算平方的「匿名函数」
# 第二个参数是列表,即该「匿名函数」作用的对象
print( map_iter )
print( list(map_iter) )
<map object at 0x0000020CBF569B38>
[1, 4, 9, 16, 25]
注意 map_iter 是 map 函数的返回对象 (它是一个迭代器),想要将其内容显示出来,需要用 list 将其转换成「列表」形式。
惰性求值
惰性求值 (lazy evaluation) 也称为传需求调用 (call-by-need),目的是最小化计算机要做的工作。
在上例中,map 函数作用到列表,并不会立即进行求平方,而是当你用到其中某些元素时才去求平方。惰性是指,你不主动去遍历它,就不会计算其中元素的值。
为什么要有 「惰性求值」呢?在本例看起来毫无必要,但试想大规模数据时,一次性处理往往抵消而且不方便,而惰性求值解决了这个问题,它把计算的具体步骤延迟到了要实际用该数据的时候。
filter
filter_iter = filter(lambda n: n % 2 == 1, lst)
# 第一个参数是一个识别奇数的「匿名函数」
# 第二个参数是列表,即该「匿名函数」作用的对象
print( filter_iter )
print( list(filter_iter) )
<filter object at 0x0000020CBF5699E8>
[1, 3, 5]
reduce
from functools import reduce
reduce( lambda x,y: x+y, lst )
# 第一个参数是一个求和相邻两个元素的「匿名函数」
# 第二个参数是列表,即该「匿名函数」作用的对象
15
# 在 reduce 函数的第三个参数还可以赋予一个初始值,
reduce(lambda x,y: x+y, lst, 100)
115
除了 Python 这些内置函数,我们也可以自己定义高阶函数,
def apply_to_list( fun, some_list ):
return fun(some_list)
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
这个 apply_to_list 函数和上面的 map, filter 和 reduce 的格式类型,第一个参数 fun 是可以作用到列表的函数,第二个参数是一个列表。
闭包
Python 里面的闭包 (closure) 属于第二种高阶函数,返回值是函数。
在函数内部再定义一个函数,并且这个函数用到了外部函数的变量,那么就将这个函数以及用到的一些变量称之为闭包。 闭包中的return返回的是内部函数名,并且用到了外部函数的变量。
def make_counter(init):
counter = [init]
# counter是外部函数的变量,在内部函数中可以调用,但是不能修改。所以这里要先把init改写成一个列表,因为列表是可修改的
def inc(): counter[0] += 1 # 用增加子函数 inc() 续一秒
def dec(): counter[0] -= 1 # 用减少子函数 dec() 废一秒
def get(): return counter[0] # 用获取子函数 get() 看秒数
def reset(): counter[0] = init # 用重置子函数 reset() 回原点
return inc, dec, get, reset
inc, dec, get, reset = make_counter(0)
inc()
inc()
inc()
get()
3
dec()
get()
2
def make_counter2(init):
counter = init
# counter是外部函数的变量,在内部函数中可以调用,但是不能修改。
def inc(): counter += 1 # 内部函数不能直接修改外部函数的变量,所以调用这个函数的时候会报错
def dec(): counter -= 1
def get(): return counter
def reset(): counter = init
return inc, dec, get, reset
inc, dec, get, reset = make_counter2(100)
inc()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-97-52d53d3893be> in <module>
1 inc, dec, get, reset = make_counter2(100)
----> 2 inc()
<ipython-input-96-3c5ac6bf2a7e> in inc()
3 # counter是外部函数的变量,在内部函数中可以调用,但是不能修改。
4
----> 5 def inc(): counter += 1 # 内部函数不能直接修改外部函数的变量,所以调用的时候会报错
6 def dec(): counter -= 1 #
7 def get(): return counter #
UnboundLocalError: local variable 'counter' referenced before assignment
def make_counter2(init):
counter = init
# counter是外部函数的变量,在内部函数中可以调用,但是不能修改。
def inc():
nonlocal counter # 标明counter是外部函数的变量
counter += 1 # 外部变量counter +1
def dec():
nonlocal counter # 标明counter是外部函数的变量
counter -= 1 # 外部变量counter +1
def get():
return counter # 直接调用外部变量counter,没有修改,所以不需要标注
def reset():
nonlocal counter # 标明counter是外部函数的变量
counter = init # 对外部变量counter进行重新赋值
return inc, dec, get, reset
inc, dec, get, reset = make_counter2(100) # 创建4个函数
inc()
get()
101
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]
sorted( lst, reverse=True )
[5, 4, 3, 2, 1]
这样每次设定参数很麻烦,可以专门为「降序排列」的应用定义一个函数,比如叫 sorted_dec,用偏函数 partial 把内置的 sorted 函数里的 reverse 固定住
sorted_dec = partial( sorted, reverse=True ) # 第一个参数是sorted函数,第二个参数是sorted函数的一个参数“reverse=True”
sorted_dec
functools.partial(<built-in function sorted>, reverse=True)
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
def add2(x):
def add(y):
return x + y
return add
- add1:参数是 x 和 y,输出 x + y
- add2:参数是 x,输出 x + y
- g = add2(2):参数是 y,输出 2 + y
g = add2(2) # g是一个函数,一个输入y,输出2+y的函数
g(1) #在函数g()中输入1,然后输出2+1
3
def minus2(x):
def minus(y):
return x - y
return minus
g2 = minus2(5) # g是一个函数,一个输入y,输出x-y的函数
g2(1) #在函数g2()中输入1,然后输出5-1
4
add1
<function __main__.add1(x, y)>
add2
<function __main__.add2(x)>
add2(2)
<function __main__.add2.<locals>.add(y)>
5 解析式
5.1 大框架
解析式 (comprehension) 是将一个可迭代对象转换成另一个可迭代对象的工具。
- 第一个可迭代对象:可以是任何容器类型数据。
- 第二个可迭代对象:看是什么类型解析式:
- 列表解析式:可迭代对象是 list
- 字典解析式:可迭代对象是 dict
- 集合解析式:可迭代对象是 set
列表、字典和集合解析式的伪代码 (pseudo code)如下:
- 列表解析式(list comprehension)
- [值 for 元素 in 可迭代对象 if 条件]
- 字典解析式(dict comprehension)
- {键值对 for 元素 in 可迭代对象 if 条件}
- 集合解析式(set comprehension)
- {值 for 元素 in 可迭代对象 if 条件}
解析式 input-operation-output 这个过程总结:
- input:任何「可迭代数据 A」
- operation:用 for 循环来遍历 A 中的每个元素,用 if 来筛选满足条件的元素 Agood
- output:将 Agood 打包成「可迭代数据」,生成列表用 [],生成字典和集合用 {}
5.2 列表解析式
问题1:如何从一个含整数列表中把奇数 (odd number) 挑出来?
lst = [1, 2, 3, 4, 5] # 现在有一个列表lst
odds = [] # 定义一个空列表odds,用来装奇数
for n in lst:
# 循环遍历整个列表
if n % 2 == 1:
# 如果列表中的某一个数n,除以2的余数是1
odds.append(n)
# 那么就将这个数放到列表odds中
odds
[1, 3, 5]
odds = [n for n in lst if n % 2 == 1] # 列表解析式:对于列表lst中的每一个元素,如果是奇数,则返回这个数,并最终组合成一个新的列表
odds
[1, 3, 5]
可以把「for 循环」到「解析式」的过程想像成一个「复制-粘贴」的过程:
- 1.将「for 循环」的新列表复制到「解析式」里
- 2.将 append 里面的表达式 n * 2 复制到新列表里
- 3.复制循环语句 for n in lst 到新列表里,不要最后的冒号
- 4.复制条件语句 if n%2 == 1 到新列表里,不要最后的冒号
问题2:如何用「列表解析式」将一个二维列表中的元素按行一个个展平?
m = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
flattened = [] # 创建一个空列表,用来装展开后的列表
for row in m:
# 对列表中的每一行(子列表)进行循环
for n in row:
# 对每一行(子列表)中的每一个元素进行循环
flattened.append(n)
# 把子列表中的每一个元素都装到新列表flattered中
flattened
[1, 2, 3, 4, 5, 6, 7, 8, 9]
flattened2 = [n for row in m for n in row]
flattened2
[1, 2, 3, 4, 5, 6, 7, 8, 9]
两点需要注意:
- 该例没有「if 条件」条件,或者认为有,写成「if True」。如果有「if 条件」那么直接加在「内 for 循环」后面。
- 「外 for 循环」写在「内 for 循环」前面。
我们把「列表解析式」那一套举一反三的用到其他解析式上,用下面两图理解一下「字典解析式」和「集合解析式」。
字典解析式
集合解析式
回顾三种解析式,我们发现其实它们都可以实现上节提到的 filter 和 map 函数的功能,用专业计算机的语言说,解析式可以看成是 filter 和 map 函数的语法糖。
- 语法糖 (syntactic sugar):指计算机语言中添加的某种语法,对语言的功能没有影响,但是让程序员更方便地使用。
- 语法盐 (syntactic salt):指计算机语言中添加的某种语法,使得程序员更难写出坏的代码。
- 语法糖浆 (syntactic syrup):指计算机语言中添加的某种语法,没能让编程更加方便。
两者都是把原列表根据某些条件转换成新列表
- 「列表解析式」用 if 条件来做筛选得到 item,再用 f 函数作用到 item 上。
- 「map/filter」用 filter 函数来做筛选,再用 map 函数作用在筛选出来的元素。
为了达到相同目的,明显「列表解析式」是种更简洁的方式。
用「在列表中先找出奇数再乘以 2」的例子,对于列表 lst = [1, 2, 3, 4, 5],我们先看「列表解析式」的实现:
lst = [1, 2, 3, 4, 5]
[ n*2 for n in lst if n%2 == 1]
[2, 6, 10]
list(map(lambda n : n*2, filter(lambda n: n%2 == 1, lst)))
[2, 6, 10]
5.3 实例
问题1:用解析式将二维元组里每个元素提取出来并存储到一个列表中。
tup = ((1, 2, 3), (4, 5, 6), (7, 8, 9))
[n for t in tup for n in t]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
把上述列表改成一个二位列表
lst = [] # 创建一个大列表
for t in tup:
# 对元组里面的每一个子元组进行循环
lst2 = [] # 创建一个子列表,用来装子元组里面的元素
for n in t:
# 对子元组里面的每一个元素进行循环
lst2.append(n)
# 将子元组里面的每一个元素转到子列表里面
lst.append(lst2)
# 将子列表装到大列表里面
lst
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[n for n in t] for t in tup]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
问题2:用解析式把以下这个不规则的列表 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]
def f(x):
if type(x) is list:
a = []
for l in x:
for y in f(l):
a.append(y)
return a
else:
return [x]
f(a)
[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(x) 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 的功能。