一、高阶函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

1
2
3
def add(x, y, f):
return f(x) + f(y)
print(-10, 5, abs)

1.1 map

map()函数接收一个函数名和n个可迭代对象。

1
2
3
4
5
6
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
1
2
3
4
5
6
a = [1,2,3,4,5]
b = [2,3,4,5,6]
def sum_num(x, y):
return x + y
sum_a_b = map(sum_num, a, b)
print(list(sum_a_b))

1.2 reduce

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

1
2
3
4
5
6
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

当然求和运算可以直接用Python内建函数sum(),没必要动用reduce

但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579reduce就可以派上用场:

1
2
3
4
5
6
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:

1
2
3
4
5
6
7
8
9
10
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579

整理成一个str2int的函数就是:

1
2
3
4
5
6
7
8
9
10
from functools import reduce

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 fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))

还可以用lambda函数进一步简化成:

1
2
3
4
5
6
7
8
9
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
return DIGITS[s]

def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))

也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!

1.3 filter

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

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

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

把一个序列中的空字符串删掉,可以这么写:

1
2
3
4
5
def not_empty(s):
return s and s.strip() # strip() 两边去掉空白

list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

1.4 sorted

Python内置的sorted()函数就可以对list进行排序:

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

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

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

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:

1
2
3
list = [36, 5, -12, 9, -21]

keys = [36, 5, 12, 9, 21]

然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:

1
2
3
keys排序结果 => [5, 9,  12,  21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]

我们再看一个字符串排序的例子:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给sorted传入key函数,即可实现忽略大小写的排序:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

二、匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Students():
def __init__(self, name, age):
self.name = name
self.age = age

stu1 = Students('zhangsan', 21)
stu2 = Students('lisi', 22)
stu3 = Students('wangwu', 23)
result_list = sorted([stu1, stu2, stu3], key=lambda x: x.age)
for stu in result_list:
print(stu.name)
# zhangsan
# lisi
# wangwu

三、闭包

1. 什么是闭包?

闭包其实就是一个函数

2. 如何创建闭包?

a. 有函数的嵌套;b. 内部函数中要使用外部函数的变量;c. 外部函数必须有返回值,返回内部函数名

3. 如何使用闭包?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用闭包,完成两个数的和
def funOut(num1):
def funIn(num2): # 函数嵌套
# 内部函数修改外部变量
# nonlocal num1
# num += 100
return num2 + num1 # 内部函数使用外部函数的变量
return funIn #返回内部函数名

# 闭包的使用
f = funOut(100)
print(type(f))
result = f(200)
print(result)

# <class 'function'>
# 300

1.1 闭包的特殊用途

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
#闭包的特殊用途:不修改源代码的前提下,添加新的功能
#添加日志输出信息
import time
def writeLog(func):
try:
file=open('wirteLog.txt','a',encoding='utf-8')
#向文件中写入日志信息(访问:方法名 时间:XXXX-XX-XX)
file.write('访问:')
file.write(func.__name__)
file.write('\t')
file.write('时间:')
file.write(time.asctime())
file.write('\n')
except Exception as e:
print(e.args)
finally:
file.close()
def fun1():
# writeLog(fun1)
print('功能1')
def fun2():
# writeLog(fun2)
print('功能2')

#使用闭包,不修改fun1 和fun2的功能代码,添加日志信息
def funcOut(func):
def funcIn():
writeLog(func)
func()
print('调用',func.__name__,'结束')
return funcIn

fun1=funcOut(fun1)
fun1()
fun2=funcOut(fun2)
fun2()

四、装饰器

4.1 装饰器的基本使用

装饰器的基本使用简化了闭包的调用通过@funcOut直接完成了fun1=funcOut(fun1),fun2=funcOut(fun2)

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
#使用装饰器 完成不修改fun1() fun2()函数的源码,添加输出日志信息
import time


def writeLog(func):
try:
file=open('log.txt','a',encoding='utf-8')
file.write('访问:')
file.write(func.__name__)
file.write('\t')
file.write('时间:')
file.write(time.asctime())
file.write('\n')
except Exception as e:
print(e.args)
finally:
file.close()

#使用闭包
def funcOut(func):
def funcIn():
writeLog(func)
func()
return funcIn
#闭包的调用

@funcOut #fun1=funcOut(fun1)
def fun1():
print('功能1')

@funcOut #fun2=funcOut(fun2)
def fun2():
print('功能2')
fun1()
fun2()

4.2 多个修饰器的作用

多个修饰器的使用注意,修饰器的顺序问题,按照解释型语言的执行顺序添加修饰器即可

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
#使用多个装饰器

# 闭包
def funOut(fun):
def funIn():
print('Hello')
fun()
return funIn
# 闭包
def funOut1(fun):
def funIn():
print('I am foo')
return funIn
# 装饰器
@funOut
@funOut1
def func():
print('foo 正在运行')

func()

## 结果如下
Hello
I am foo
foo 正在运行

4.3 带参数的装饰器

注意内函数传参即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
def writeLog(fun):
print('正在调用函数', fun.__name__, '/t time:', time.asctime())

# 闭包
def funOut(fun):
def funIn(x, y):
writeLog(fun)
return fun(x, y)
return funIn
# 修饰器
@funOut
def sum_num(x, y):
result = x + y
return result
result = sum_num(1, 2)
print(result)

通用装饰器

1
2
3
4
5
6
# 闭包
def funOut(fun):
def funIn(*args, **kwargs):
writeLog(fun)
return fun(*args, **kwargs)
return funIn

4.4 偏函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
print(int('12345')) #将字符串按十进制转换为整数
print('八进制转换为十进制:',int('12345',base=8))
print('十六进制转换为十进制:',int('12345',16))
#将'1010' 按二进制转换为整数
print(int('1010',base=2))
print(int('101010',base=2))
print(int('10101010',base=2))
#定义函数
def new_int(s):
return int(s,base=2)
print(new_int('1010'))
print(new_int('101010'))
print(new_int('10101010'))
#
from functools import partial
new_int=partial(int,base=2)
print(new_int('1010'))
print(new_int('101010'))
print(new_int('10101010'))