Python3函数式编程(v3.7)

[TOC]

高阶函数

函数的特点:

  • 变量可以指向函数
1
2
3
>>> f = abs #把内置函数abs()函数本身赋值给变量
>>> f(-10)
10

调过变量f调用abs函数结果与abs函数结果相同,说明变量f已指向abs函数本身

  • 函数名也是变量
1
2
3
4
5
abs = 10
abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

把10赋值给函数名abs,结果abs就无法调用该函数了!因为abs也是一个变量,此时变量abs已经不指向求绝对值函数,而是指向一个整数10

高阶函数就是能接受函数作为参数的函数。

1
2
3
4
5
6
7
8
def add(x,y,f) : #定义一个最简单的高阶函数
return f(x) + f(y)

#调用函数传入参数为函数
x = -5
y = 6

add(x,y,abs) ==> abs(-5) + abs(6) --> 11

内置高阶函数

map/reduce

  • map函数
    该函数接收两个参数一个是函数,另一个是Iterable对象。map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator对象返回。
1
2
3
4
5
def func(x) : #定义一个求数平方的函数
    return x * x

r = map(func,[x for x in range(10)])
print(list(r)) --> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  • reduce函数
    该函数是把一个函数作用在一个序列上,这个函数必须接收两个参数。reduce把结果继续和序列的下一个元素做累计计算。效果如:reduce(f,[x1,x2,x3,x4,x5])=f(f(f(f(x1,x2),x3),x4),x5)
1
2
3
4
5
6
7
#例如把序列[1, 3, 5, 7, 9]变换成整数13579
from functools import reduce
def func(x,y) :
return x * 10 + y

调用:
reduce(func,[1,3,5,7,9]) --> 13579

filter

接收两个参数,一个函数,一个序列。filter根据把传入的函数作用于序列的每个元素,根据返回值True或False来决定保留或丢弃该元素,并把结果作为Iterator返回。

1
2
3
4
5
def is_odd(x) :
return x % 2 == 0

list(filter(is_odd,[1,2,3,4,5,6,7,8,9,10]))
--> [2, 4, 6, 8, 10]

sorted

sorted可以直接对list进行自然排序:

1
2
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

sorted高阶用法接收三个参数:排序列表, [keys=自定义函数], [reverse=True|False]
自定义函数(可选参数)将会作用于每个元素后再进行排序。
reverse(可选参数)将会排序列表顺序反转。

1
2
3
4
5
6
7
8
9
10
11
#普通排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

#高阶排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

#反转高阶排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

闭包

闭包就是能够读取其他函数内部变量的函数。这种结构通常只有函数的子函数才能读取局部变量,所以闭包可以理解为“定义在一个函数内部的函数”。

1
2
3
4
5
6
#定义求和函数数
def calc_sum(*args) :
ax = 0
for n in args :
ax = ax + n
return ax

上面函数调用会立即求和,如果想要定义一个函数,而不立刻求值,在需要时再计算,此时即可采用闭包:

1
2
3
4
5
6
7
def lazy_sum(*args) :
def sum() :
ax = 0
for n in args :
ax = ax + n
return ax
return sum

当调用lazy_sum()时返回的不是求和结果而是sum()函数。
调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

1
2
3
4
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

注意:返回闭包时,返回函数不要引用任何循环变量,或者后续会发生变化的变量。因为闭包是延迟执行的,可能发生与预期不同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#闭包引用循环变量
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

>>> f1() #预期结果 --> 1
9
>>> f2() #预期结果 --> 4
9
>>> f3() #预期结果 --> 9
9 --> 9

| |
| |
| |
V V
#代码改进
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

匿名函数

当传入函数时,有些时候不需要显示定义函数,直接传入匿名函数更方便。因此Python中支持lambda匿名函数。
语法:lambda : 函数参数 : 函数返回值表达式

匿名函数只能有一个表达式,不用写retrun,返回值就是该表达式的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
#匿名函数赋值
>>> f = lambda x : x * x
>>> f(2)
4

-----------------------------

#匿名函数作为返回值
def func(x) :
return lambda : x * x

>>> func(2)()
4

装饰器

Python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。

  • #定义一个简单装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#计算方法执行时间装饰器
import time

def timer(func):
def wrapper(*args, **kw):
start_time = time.time()
func(*args,**kw)
stop_time = time.time()
print('%s()方法运行时间:%s' % (func.__name__, stop_time-start_time))
return wrapper

@timer
def test1() :
time.sleep(1)

test1()

timer()方法是一个装饰器,test()方法通过@装饰器名方法引用装饰器。原来test()函数仍然存在,只是指向了timer()返回函数wrapper()。wrapper()函数的参数(*args,**kw)表示接收任何参数调用。

1
2
3
#不带参数装饰器执行效果
>>> @timer #相当于执行:test = timer(test)
>>> test() #相当于执行:timer(test)()
  • 定义一个带参数的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#打印log日志装饰器
def log(level):
def decorator(func):
def wrapper(*args, **kw):
print('[[%s]] 执行%s()' % (level, func.__name__))
return func(*args, **kw)
return wrapper
return decorator


@log(level='DEBUG')
def test2():
print('Hello test2')

test2()

和不带参数装饰器比带有参数的装饰器多了一层嵌套,执行效果如下:

1
2
3
#带参数装饰器执行效果
>>> @log(level='DEBUG') #相当于执行log(level='DEBUG')(test2)
>>> test2() #相当于执行log(level='DEBUG')(test2)()

以上两个函数test1()和test2()经过装饰器装饰之后变量指向被改变,所以它们的__name__属性已经从原来的test1和test2变成了wrapper。所以需要把原始函数的属性复制到wrapper()函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> test1.__name #名称已经被改变
'wrapper'

| 优 |
| 化 |
| 代 |
V 码 V

#使用Python内置的functools.wraps可以解决此问题,需要import functools导入functools
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kw):
start_time = time.time()
func(*args,**kw)
stop_time = time.time()
print('%s()方法运行时间:%s' % (func.__name__, stop_time-start_time))
return wrapper

偏函数

偏函数是Python的functools模块中的一个函数,它和数学中的偏函数不是一个概念。
函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在调用之前提前获知。这种情况一个函数有一个或多个参数预先就可以使用,以便函数能更少的参数进行调用,functools.partial就可以实现这个功能。

如:int()函数可以将字符串转换为整数

1
2
3
4
>>> int('123')
123
>>> int('123',base=16) #传入base参数默认值是10,如果传入其它值,就可以做N进制的转换
291

如果要转换大量N进制字符串,那么每次都要传入base参数,于是就可以利用partical定义一个intN()函数来减少每次传入参数。

1
2
3
4
#定义一个二进制转换函数
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64