Python学习笔记(四)函数式编程

时间:2022-11-02 11:53:32

高阶函数(Higher-order function)

Input:

1
abs

Output:

1
<function abs>

Input:

1
abs(-10)

Output:

1
10

abs是函数本书,abs(-10)是函数调用

Input:

1
2
f = abs
f

Output:

1
<function abs>

变量可以指向函数

Input:

1
f(-10)

Output:

1
10

函数名也是变量

函数名其实就是指向函数的变量,对于abs()这个函数,完全可以把abs看成变量,它指向一个可以计算绝对值的函数,如果把abs指向其他对象,将无法再调用计算绝对值的函数

Input:

1
2
abs = 10
abs(-10)

Output:

1
2
3
4
5
6
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-dae1c1c42fe8> in <module>()
1 abs = 10
----> 2 abs(-10)
TypeError: 'int' object is not callable

abs函数实际上是定义在import builtins模块中的

传入函数

一个函数接收另外一个函数作为参数,这种函数就称之为高阶函数

Input:

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

Output:

1
11

map/reduce

基本知识

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

Input:

1
2
3
4
5
def f(x):
return x*x r = map(f, [1,2,3,4,5,6,7,8,9])
list(r)

Output:

1
[1, 4, 9, 16, 25, 36, 49, 64, 81]

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

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

对一个序列求和,就可以用reduce实现

Input:

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

Output:

1
25

我们配合map(),可以写出把str转换为int的函数:

Input:

1
2
3
4
5
6
7
8
9
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'))

Output:

1
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))

练习

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:

Input:

1
2
3
4
def normalize(name):
def specilization(s):
return s.casefold().capitalize()
return map(specilization, name)
1
2
res = normalize(['adam', 'LISA', 'barT'])
list(res)

Output:

1
['Adam', 'Lisa', 'Bart']

Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

Input:

1
2
3
from functools import reduce
def prod(L):
return reduce(lambda x,y: x*y, L)
1
prod([3,5,7,9])

Output:

1
945

利用mapreduce编写一个str2float函数,把字符串123.456转换成浮点数123.456

Input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import reduce
TRANS = {'1':1, '2':2, '3':3, '4':4, '5':5, '6':6}
def str2float(s):
s2 = s.split('.')
def point_left(x, y):
return x*10 + y
def char2num(s):
return TRANS[s]
left2int = reduce(point_left, map(char2num, s2[0]))
right2int = reduce(point_left, map(char2num, s2[1]))
while(right2int > 1):
right2int = right2int/10
return left2int + right2int
1
2
3
4
5
print('str2float('123.456') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')

Output:

1
2
str2float('123.456') = 123.456
测试成功!

filter

基本知识

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

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

Input:

1
2
3
4
def is_odd(n):
return n % 2 ==1 list(filter(is_odd, [1,2,4,5,6,9,10,15]))

Output:

1
[1, 5, 9, 15]

把一个序列中的空字符串删掉

Input:

1
2
3
4
def not_empty(s):
return s and s.strip() list(filter(not_empty, ['A','','B',None,'C',' ']))

Output:

1
['A', 'B', 'C']

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

用filter求素数

用python实现使用埃氏筛法计算素数

可以先构造一个从3开始的奇数序列,这是一个生成器,并且是一个无限序列

1
2
3
4
5
def _odd_iter():
n = 1
while True:
n = n+2
yield n

然后定义一个筛选函数

1
2
def _not_divisible(n):
return lambda x: x%n > 0

最后,定义一个生成器,不断返回下一个素数

1
2
3
4
5
6
7
def primes():
yield 2
it = _odd_iter() #初始序列
while True:
n = next(it) #返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) #构造新序列
1
2
3
4
5
6
# 打印30以内的素数:
for n in primes():
if n < 30:
print(n)
else:
break

Output:

1
2
3
4
5
6
7
8
9
10
2
3
5
7
11
13
17
19
23
29

注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

练习

回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:

Input:

1
2
def is_palindrome(n):
return str(n) == str(n)[::-1]
1
2
3
4
5
6
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 大专栏  Python学习笔记(四)函数式编程ber">141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')

Output:

1
2
1~1000: [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]
测试成功!

filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。

sorted

基本知识

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

Input:

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

Output:

1
[-21, -12, 5, 9, 36]

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

Input:

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

Output:

1
[5, 9, -12, -21, 36]

对字符串进行排序

Input:

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

Output:

1
['Credit', 'Zoo', 'about', 'bob']

实现忽略大小写的排序

Input:

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

Output:

1
['about', 'bob', 'Credit', 'Zoo']

进行反向排序

Input:

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

Output:

1
['Zoo', 'Credit', 'bob', 'about']

练习

假设我们用一组tuple表示学生名字和成绩:
L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
请用sorted()对上述列表分别按名字排序:

Input:

1
2
3
4
5

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return sorted(L, key = lambda x: x[0])
1
by_name(L)

Output:

1
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

再按成绩从高到低排序

Input:

1
2
def by_score(t):
return sorted(L, key = lambda x: x[1])
1
by_score(L)

Output:

1
[('Bart', 66), ('Bob', 75), ('Lisa', 88), ('Adam', 92)]

返回函数

基本知识

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

定义一个求和函数,不需要立刻得到求和结果,而是在后面的代码中根据需要再计算。

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()时,返回的并不是求和结果,而是求和函数

Input:

1
f = lazy_sum(1,3,5,7,9)
1
f

Output:

1
<function __main__.lazy_sum.<locals>.sum>

当调用函数f时,才真正计算求和的结果

Input:

1
f()

Output:

1
25

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

闭包

注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。

Input:

1
2
3
4
5
6
7
8
9
def count():
fs = []
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs f1, f2, f3 = count()

Input:

1
f1()

Output:

1
9

Input:

1
f2()

Output:

1
9

Input:

1
f3()

Output:

1
9

我们可能会认为调用f1()f2()f3()结果应该是149,但实际结果全是9。原因就在于返回的函数引用了变量i,但它非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环表按量后续如何更改,已绑定到函数参数的值不变:

Input:

1
2
3
4
5
6
7
8
9
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
1
f1, f2, f3 = count()

Input:

1
f1()

Output:

1
1

Input:

1
f2()

Output:

1
4

Input:

1
f3()

Output:

1
9

练习

利用闭包返回一个计数器函数,每次调用它返回递增整数:

Input:

1
2
3
4
5
6
7
8
9
10
11
def createCounter():
def f():
i = 0
while True:
i = i +1
yield i
sun = f()
def counter():
return next(sun)
return counter
1
2
3
4
5
6
7
8
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')

Output:

1
2
1 2 3 4 5
测试通过!

匿名函数

基本知识

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。关键字lambda表示匿名函数,冒号前面的x表示函数参数

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

Input:

1
2
f = lambda x: x*x
f(5)

Output:

1
25

练习

请用匿名函数改造下面的代码:

Input:

1
2
3
4
5
def is_odd(n):
return n % 2 == 1 L = list(filter(is_odd, range(1, 20)))
1
L

Output:

1
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Input:

1
2
L = list(filter(lambda x: x%2 == 1, range(1, 20)))
1
L

Output:

1
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

装饰器

假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处

1
2
3
def now():
print('2018.01.05')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志

Input:

1
now()

Output:

1
2
call now():
2018.01.05

@log放到now()函数的定义处,相当于执行了语句now=log(now)

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本

1
2
3
4
5
6
7
def log(text):
def decorator(func):
def warpper(*args, **kw):
print('%s %s():' %(text, func.__name__))
return func(*args, **kw)
return warpper
return decorator
1
2
3
@log('execute')
def now():
print('2015-3-25')

Input:

1
now()

Output:

1
2
execute now():
2015-3-25

和两层嵌套的decorator相比,三层嵌套的效果是now = log('execute')(now)

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

创建一个把二进制的字符串转换为整数的偏函数

Input:

1
2
3
import functools
int2 = functools.partial(int, base=2)
int2('10000')

Output: