Lesson 16

  • v1.1, 2020.5.6 edit by David Yi

本次内容

  • 将函数作为值返回
  • 高阶函数 map/reduce filter sorted
  • 匿名函数
  • 偏函数

In [4]:
def lazy_sum(*args):
    
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    
    return sum

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


25

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

一个函数可以返回一个计算结果,也可以返回一个函数。

返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。


In [10]:
# 进制转换函数

print(int(12345))
print(int('1000',base=2))
print(int('1A',base=16))


12345
8
26

虽然默认参数还是很容易使用,但是如果我们在某个场景需要大量调用的话,还是有点方便,特别是对于有很多参数的函数来说,会让程序先的复杂。还记得之前那个 max min 的程序么?


In [12]:
import functools

int2 = functools.partial(int, base=2)

print(int2('1000000'))
print(int2('1010101'))


64
85

In [22]:
# 偏函数举例

def func(x=2,y=3,z=4):
    return x+y+z

print(func(x=3))
print(func(y=6))
print(func(x=4,y=10))
print(func(2,3))


10
12
18
9

In [27]:
import functools

f1 = functools.partial(func, z=2)
print(f1(2,3))
print(f1(2))
print(f1(2,3,4)) # 会报错,不需要再输入 z 的值


7
7
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-6509675d1fa0> in <module>()
      4 print(f1(2,3))
      5 print(f1(2))
----> 6 print(f1(2,3,4))

TypeError: func() got multiple values for argument 'z'

In [29]:
# 思考一下

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(a[1])
print(a[-1])


1
10

In [31]:
print(a[::2])
print(a[::-2])


[0, 2, 4, 6, 8, 10]
[10, 8, 6, 4, 2, 0]

In [28]:
a = ['a', 'b', 'ab', 'abcd']
print("".join(a))
print("-".join(a))


abababcd
a-b-ab-abcd
map() 函数

Python内建了 map() 和 reduce() 这两个功能强大的函数。

我们先看 map。map() 函数接收两个参数,一个是函数,一个是可迭代的序列,比如 list,map 将传入的函数依次作用到序列的每个元素,并把结果作为新的 Iterator 可迭代值返回。

举例说明,比如我们有一个函数 f(x) = x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。在 hadoop 时代,map/reduce 的理念是高效并行运算的核心。


In [3]:
# map 函数举例

def f(x):
    return x * x

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])

for i in r:
    print(i)


1
4
9
16
25
36
49
64
81

In [2]:
# 这个 f(x) 函数可以更加复杂

def f(x):
    y = x * x + 3
    return y

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])

for i in r:
    print(i)


4
7
12
19
28
39
52
67
84

In [6]:
# 进行 map 处理的数据也可以很复杂

def f(x):
    y = x * x + 3
    return y

l = [x for x in range(1,100,7) if x % 2 ==0]

print(l)

# 主要的程序还是很简洁
r = map(f, l)

for i in r:
    print(i)


[8, 22, 36, 50, 64, 78, 92]
67
487
1299
2503
4099
6087
8467

reduce() 函数

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

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


In [8]:
# reduce 举例,一个加法函数
from functools import reduce

def add(x, y):
    return x + y

print(reduce(add, [1, 3, 5, 7, 9]))


25

In [22]:
# reduce,模拟一个字符串转换为整数的函数

from functools import reduce

def f(x, y):
    return x * 10 + y

# 将字符转换为数字
def char2int(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]

print(reduce(f, map(char2int, '13579')))


13579

In [18]:
# 先 map

l = map(char2num, '13579')
for i in l:
    print(i,type(i))


1 <class 'int'>
3 <class 'int'>
5 <class 'int'>
7 <class 'int'>
9 <class 'int'>

In [20]:
# 再 reduce

l = map(char2num, '13579')
print(reduce(fn,l))


Out[20]:
13579

In [1]:
# 将字符串转换成整数的函数再包装一下

from functools import reduce

def f(x, y):
    return x * 10 + y

def char2int(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, 
            '6': 6, '7': 7, '8': 8, '9': 9}[s]

def str2int(s):
    return reduce(f, map(char2int, s))

print(str2int('12345'))


12345

filter() 函数

Python内建的filter()函数用于过滤序列。

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


In [6]:
# filter 举例,在一个list中,删掉偶数,只保留奇数

# 判断是否是奇数
def is_odd(n):
    return n % 2 == 1

print(list(filter(is_odd, [1, 2, 4, 5, 6])))


[1, 5]

In [19]:
# 筛选一个 list 中为空的元素

def not_empty(s):
    # strip() 用于移除字符串头尾指定的字符(默认为空格)
    if len(s.strip()) ==0:
        return False
    else:
        return True

print(list(filter(is_empty, ['A', '', 'B','C', '  '])))


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

In [21]:
# 返回一定范围内既不能被2整除也不能被3整数的数字

def f(x): 
    return x % 2 != 0 and x % 3 != 0

print(list(filter(f, range(2, 30))))


[5, 7, 11, 13, 17, 19, 23, 25, 29]

高阶函数小结

使用高阶函数可以让代码简洁优雅,好的函数,可以使程序修改和调试都变得容易。

函数式编程,相对传统方式比较难理解,但是这样的确比较 pythonic,符合 python 的发展。

对于一些独立的功能、常用的功能,并且有很明显的输入和输出,放到独立函数中,是比较好的做法。

一般来说,一个函数的代码不要超过20行。

复杂的项目,一般要采用面向对象的开发方式,才能有利于维护和更新。


python 函数式编程的基本功能点:

函数作为返回值 偏函数 高阶函数 map/reduce, filter, sorted 匿名函数 lambda


In [ ]:


In [ ]: