函数式编程,即 Functional Programming,有以下几个特点:
Python 对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数,即 Higher-order function,有几个特点:
变量可以指向函数
这里以 abs()
函数来作说明:
print(abs(-10)) # 10
# 直接打印 abs 呢?
print(abs) #
# 把函数本身赋值给变量
f = abs
print(f) #
# 使用变量 f 来调用 abs() 函数
print(f(-100)) # 100
可以看到,可以用变量指向函数,并且可以通过这个变量来调用函数。
函数名也是变量
函数名其实就是指向函数的变量。
通过 abs()
这个内置函数进行说明,这个函数的作用是计算绝对值。这里我们完全可以把 abs
这个函数名看作变量,它指向了一个可以计算绝对值的函数。既然 abs
是一个变量,当然可以指向其他对象。
# 函数名也是变量
# abs = 10
# print(abs(-10)) # 报错
'''
Traceback (most recent call last):
File "higher_order_function_intro.py", line 13, in
print(abs(-10))
TypeError: 'int' object is not callable
'''
上面我们把 abs
这个函数名,当做变量,指向了 10
。接着,又期望 abs(-10)
可以得出结果 10
。实际上,是报错的。其实现在 abs
是指向了 10
,而不再指向绝对值函数了。这一点是需要注意的。
传入函数
既然变量可以指向函数,而函数的参数可以接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就是高阶函数。
# -----传入函数------
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs)) # 11
map
函数接收多个参数,第一个是函数,后面的参数是一个或多个Iterable
,用逗号隔开,返回一个 Iterator
。map
函数语法:
map(function, iterable, …)
map
函数将传入的函数依次作用到Iterable
中的每个元素上,把结果作为 Iterator
返回。map
函数是 Python 中的内置函数。
from collections.abc import Iterator, Iterable
# map() 函数
# 把 f(x) = x ^ 2, 作用在一个 list [1,2,3,4,5,6,7,8,9] 上
def f(x):
return x ** 2
r = map(f, [1,2,3,4,5,6,7,8,9])
print(isinstance(r, Iterator)) # True,说明 r 是 Iterator。
print(isinstance(r, Iterable)) # True,说明 r 是 Iterable。
# Iterator 是一个惰性序列
# 迭代方式一:
print(list(r))
# 迭代方式二:
for y in r:
print(y)
利用 map
函数,把 list
中的每个整数转为字符串:
print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:['1', '2', '3', '4', '5', '6', '7', '8', '9']
利用 map 函数,对两个列表相同位置的元素进行相加
print(list(map(lambda x, y : x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))) # [3, 7, 11, 15, 19]
reduce
函数:第一个参数是函数,第二个参数是 Iterable
,第三个参数是可选参数,reduce
函数的返回值与作为第一个参数的函数的返回值是一致的。需要注意的是,作为第一个参数的函数必须接收两个参数。reduce
函数的语法:
reduce(function, sequence [, initial] )
reduce
函数的作用是如果不提供 initial
参数,则先用函数对可迭代序列中的第一个元素和第二个元素进行操作,得到一个结果,再用函数对上一步得到的结果和第三个元素进行操作,以此类推,直到达到最后的结果为止。如果提供 initial
参数,则先用函数对 initial
的值和可迭代序列中的第一个元素进行操作。
reduce
函数并不是 Python 的内置函数,使用 reduce
函数需要先进行导入:
from functools import reduce
计算列表的和:
def add(x, y):
return x + y
print(reduce(add, [1, 2, 3, 4, 5])) # 15
把序列[1,3,5,7,9]变为整数13579:
def trans(x, y):
return 10 * x + y
print(reduce(trans, [1, 3, 5, 7, 9])) # 13579
把 str
类型 转 int
类型:
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
'5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
def char2num(ch):
return DIGITS[ch]
def fn(x, y):
return 10 * x + y
return reduce(fn, map(char2num, s))
print('1378') # 1378
上面的例子涉及了局部函数:Python 支持在函数体内定义函数,这既是局部函数。
filter
函数接收两个参数,第一个参数是函数,第二个参数是可迭代对象,返回值是迭代器对象。
filter
函数是 Python 的内置函数,filter
函数的语法为:
filter(function, iterable)
filter
函数的作用是过滤序列,将序列中的每个元素作为参数传递给函数,函数判断结果是 True
或 False
,为 True
时,则该元素保留在新的列表中,为False
时,则该元素不保留在新的列表中。
需要注意的是,当 function
参数不是 None
时,filter(function,iterable)
和生成器表达式 (item for item in iterable if function(item))
是等价的;当 function
参数是None
时,等价于 (item for item in iterable if item)
。
在一个 list 中,删掉偶数,只保留奇数:
# 判断是不是奇数的方法
def is_odd(n):
return n & 1 != 0
print(list(filter(is_odd, [1, 2, 3, 5, 8, 9, 12]))) # [1, 3, 5, 9]
在一个 list 中,删除负数,只保留整数:
print(list(filter(lambda x : x > 0, [-1, -2, 3, 4, 5]))) # [3, 4, 5]
第一个参数为 None
时,保留判断为 True
的元素:
print(list(filter(None, [0, 1, '', False, True]))) # [1, True]
sorted
函数的语法:
sorted(iterable, *, key=None, reverse=False)
第一个参数是一个可迭代对象,
第二个参数是个函数,用来处理进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
第三个参数是排序规则,reverse = True 降序 , reverse = False 升序(默认)。
返回一个新的 list。
参数列表里的 *
表示命名关键字参数,意思就是后面两个参数的名字 key
和 value
是被限定的,而且如果传递实参时,必须要带上参数名。
我们还注意到,key
和 value
这两个参数使用了默认参数。
# 对 list 进行从小到大排序
print(sorted([36, -3, 1, 20, -5])) # [-5, -3, 1, 20, 36]
# 对 list 进行从大到小排序
print(sorted([36, -3, 1, 20, -5], reverse=True)) # [36, 20, 1, -3, -5]
# 对 list 按绝对值从小到大排序
print(sorted([36, -3, 1, 20, -5], key=abs)) # [1, -3, -5, 20, 36]
函数作为返回值
以求和函数为例说明:
# 函数作为返回值,不会立即求和
def lazy_sum(*args):
# 这里定义了局部函数
def sum():
result = 0
for n in args:
result += n
return result
return sum
f = lazy_sum(1, 2, 3, 4)
print(f) # .sum at 0x7f51b0fd28c8>
print(f()) # 10
上面的代码使用了闭包,那么什么是闭包呢?
一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。
因为在上面的代码中,函数 sum
就是一个闭包,它引用了 lazy_sum
的参数 args
。
每次调用 lazy_sum
,都会返回一个新的函数,即便是传入的参数是一模一样的:
# 每次调用 lazy_sum ,都会返回一个新的函数变量
f1 = lazy_sum(1, 2, 3, 4)
f2 = lazy_sum(1, 2, 3, 4)
print(f1) # .sum at 0x7f8c919ea950>
print(f2) # .sum at 0x7f8c919ea9d8>
但是, 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
以之前学习 map
函数时,把容器中的数字,经过 f(x) = x^2,变换每一个元素,得到新的容器的例子来说明:
之前的写法是:
def f(x):
return x ** 2
print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函数的写法是:
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# 打印结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]
可以看到我们使用 lambda x: x * x
替代了函数 f
。
关键字 lambda
表示匿名函数,冒号前面的 x
表示函数参数,和函数一样,参数列表可以包含多个参数,也可以不包括任何参数;冒号后面的 x * x
表示函数体,但是匿名函数的函数体只能有一个表达式,并且不用写return
,返回值就是这个表达式的结果。
匿名函数也是函数,而函数可以赋值给变量,所以匿名函数也可以赋值给变量。
g = lambda x, y: x + y
print(g(1, 1)) # 2
匿名函数也可以作为返回值返回。
def add(x, y):
return lambda: x + y
print(add(2, 3)()) # 5
注意上面的例子中,匿名函数的参数列表为空,但是冒号可以不能省略的。
装饰器这一部分有点不好懂。
Python 中的装饰器(decorator)允许在不修改原函数定义的情况下,在代码允许期间动态添加功能。这样的好处是,抽离出大量与函数功能本身无关的代码到装饰器中并获得重用。
参考:理解 Python 装饰器看这一篇就够了
偏函数(partial function),是由 Python 的 functools
模块提供的功能。
偏函数的作用是:当函数的参数个数太多,需要简化调用时,可以使用 functools.partial
创建一个新的函数,新函数可以固定住原函数的部分参数,从而在调用时更简单。
以 int
函数把字符串转为整数的例子来说明:
大量需要一个把字符串,按二进制转为十进制的整数,可以这样写:
print(int('100000', base = 2)) # 32,把 100000 按照二进制,转为十进制是32
print(int('10000', base = 2)) # 16,把 10000 按照二进制,转为十进制是16
print(int('1000', base = 2)) # 8,把 1000 按照二进制,转为十进制是8
抽取一个函数 int2
,是这样的:
def int2(x, base=2):
return int(x, base)
print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8
使用偏函数,需要先导入 import functools
:
int2 = functools.partial(int, base = 2)
print(int2('100000')) # 32
print(int2('10000')) # 16
print(int2('1000')) # 8
print(int2('100000', base = 10)) # 100000