GuoXin Li's Blog

Python_Functional_Programming

字数统计: 3.4k阅读时长: 15 min
2020/01/12 Share

Python_Advanced_Function_Programming

通过大段代码拆成函数,通过一层一层的函数调用,可以把复杂的任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

函数式编程,可以归结到面向过程的程序设计,但其思想更接近数学计算。

越低级的语言,越接近计算机,抽象程度低,执行效率高,如 C 语言;越高级的语言,越接近计算,抽象程度高,执行效率低,如 Lisp 语言。

  • 纯函数式编程:

    函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数称之为没有副作用

  • 非纯函数式编程:

    允许使用变量的程序设计语言,由于函数内部变量状态不确定,同样的输入,可能得到不同的输出,因此这种函数是有副作用的。

函数式编程的特点:

  • 允许把函数本身作为参数传入另一个函数
  • 允许返回一个函数

Python 允许使用变量,因此,Python 不是纯函数式编程语言。

高阶函数

eg:

1
2
3
>>> f = abs
>>> f
<built-in function abs>

函数本身也可以赋值给变量,即:变量可以指向函数

1
2
3
>>> f = abs
>>> f(-10)
10

传入函数

一个函数可以接受另一个函数作为参数,这种函数称之为高阶函数

eg.

1
2
3
4
>>> def add(x, y, f):
return f(x) + f(y)
>>> add(-5, 6, abs)
11

map/reduce

map

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

eg.

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)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
1
2
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

reduce

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

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

1
2
3
4
5
>>> 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] 变换成整数 13579, reduce 就可以派上用场:

1
2
3
4
5
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
>>> 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

整理为函数就是:

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

lambda 使用方法

即匿名函数

eg

1
map(lambda x: x * x, [y for y in range(10)]

练习

  • 利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']
1
2
3
4
def FirstCap(s):	
def normalize(s):
return s.capitalize()
return map(normalize, s)
  • Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:
1
2
def Multi(L):
return reduce(lambda x, y : x * y, L)
  • 利用mapreduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456
1
2
3
4
5
6
7
8
9
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 str2float(s):
index=s.index('.')
s_len=len(s)
s=s.replace('.','')
return reduce(lambda x, y: x * 10 + y, (map(char2num, s)))/pow(10,s_len - index - 1)

Filter

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

eg:在一个list中删掉偶数,只保留奇数

1
2
3
4
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5 ,6 ,8])
# 结果: ['A', 'B', 'C']

Filter 筛选素数

构造一个从3开始的奇数序列

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

生成器

1
2
3
4
5
6
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

**Lg的区别仅在于最外层的 []()L 是一个 list,而 g 是一个 generator**

如果想要一个一个打印出来,可以通过next() 函数获得 generator 的下一个返回值:

1
2
3
4
5
6
7
8
9
10
>>> next(g)
0
>>> next(g)
1
...
...
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

斐波那契数列(Fibonacci)

1
2
3
4
5
6
7
8
# 函数版本
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n +1
return 'done'

注意

1
a, b = b, a + b

相当于:

1
2
3
t = (b, a + b) # t is a tuple
a = t[0]
b = t[1]

所以会有:

1
2
3
4
5
6
7
8
>>> fib(6)
1
1
2
3
5
8
'done'

把上面的函数 fib 变成 generator, 只需要把 print(b) 改成 yield b

1
2
3
4
5
6
7
def fib(max):
n, a , b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

此时这个函数就不再是一个普通函数,而是一个 generator

1
2
3
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

注意:

函数时顺序执行的,遇到 return 或者最后一行函数就返回。

generator 在每次调用 next() 时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行

当函数变为 generator 后,一般不用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:

1
2
3
4
5
6
7
8
9
>>> for n in fib(6)
print(n)
...
1
1
2
3
5
8

但是使用 for 循环不会拿到 generatorreturn 语句的返回值,必须使用 StopIteration 捕获错误,返回值包含在 StopIterationvalue 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> g = fib(6)
>>> while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print('Generator return value:', e.value)
break

...
1
1
2
4
5
8
Generator return value: done

杨辉三角

1
2
3
4
5
6
7
8
9
10
11
          1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
/ \ / \ / \ / \ / \
1 5 10 10 5 1
1
2
3
4
5
6
7
8
9
10
11
def triangles(n):
L = [1]
while n:
yield L
L = [ L[i] + L[i + 1] for i in range( len(L) - 1 ) ]
L.append(1)
L.insert(0, 1)
n -= 1
g = triangles(7)
for n in g:
print(n)

迭代器

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

1
2
for x in [1, 2, 3, 4, 5]:
pass

实际上完全等价于:

1
2
3
4
5
6
7
8
9
10
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

继续 Filer() 求素数

定义一个筛选函数:

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) #构造新的it序列

由于,primes() 也是一个无线序列,所以调用的时候需要设置一个退出循环的条件:

1
2
3
4
5
for n in primes():
if n < 1000:
print(n)
else:
break

练习

利用filter判断一个数是否是回数:

回数是指从左向右读和从右向左读都是一样的数,例如12332199

1
2
3
4
5
6
7
8
9
def _is_palindrome(s):
s = str(s)
return s == s[::-1]

def main():
output = filter(_is_palindrome,range(1,1000))
print(list(output))
if __name__=='_main_':
main()

Sorted

sort 与 sorted 区别

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

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

1
2
>>> sorted([38,2,23,2,1])
[1, 2, 2, 23, 38]

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

1
2
>>> sorted([31,-333,13,1],key = abs)
[1, 13, 31, -333]

对于字符串排序:

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

要实现,按照字母顺序排序,只需要对 key 函数改为忽略大小写排序

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

要进行反向排序,只需要传入第三个参数 reverse = True

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

练习

假设我们用一组tuple表示学生名字和成绩:

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

请用sorted()对上述列表分别按名字排序:

再按成绩从高到低排序:

1
2
3
4
5
6
7
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return t[0]
def by_score(t):
return t[1]
print(sorted(L,key=by_name))
print(sorted(L,key=by_score,reverse=True))

1
2
3
4
>>> print(sorted(L,key = by_name))
[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
>>> print(sorted(L,key = by_score))
[('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 range(1,4)
ax = ax + n
return ax
return sum
1
2
3
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

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

1
2
>>> f()
25

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

闭包:

1
2
3
4
5
6
7
8
def count():
fs = []
for i in range(1,4):
def f():
return i * i
fs.append(f) #没有立即执行,i 的值没有传入
return fs
f1, f2, f3 = count()
1
2
3
4
5
6
>>> f1()
9
>>> f2()
9
>>> f3()
9

全部都是9的原因,在于返回的函数引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i, 已经变成了 3,因此最终结果是 9。

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

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

1
2
3
4
5
6
7
8
9
def count():
def f(i):
def g():
return j * j
return g
fs = []
for i in range(1,4):
fs.append(f(i)) #立刻被执行,因此 i 的当前值被传入f()
return fs

结果:

1
2
3
4
5
6
7
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

练习

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

1
2
3
4
5
6
7
8
9
10
def createCounter():
def nature():
i = 0
while True:
i = i + 1
yield i
a = nature()
def counter():
return next(a)
return counter

装饰器

函数是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

1
2
3
4
5
6
7
8
9
10
11
>>> def now():
print('2016-9-11')
>>> f = now
>>> f()
2016-9-11


>>> now.__name__ #函数对象有一个 __name__ 属性,可以拿到函数的名字
'now'
>>> f.__name__
'now'

现在,假设我们要增强 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

注意:

wrapper 中的 *args, **kw 参数定义,目的是让 wrapper() 函数可以接受任意参数的调用。

带参数的 decorator 带参数就需要编写一个返回 decorator 的高阶函数,提前接受需要的参数
1
2
3
4
5
6
7
8
9
10
11
def log(text):
def decorator(func):
def wrapper(*args, **kv):
print('%s %s():' % (text, func.__name__))
return func(*args, **kv)
return wrapper
return decorator

@log('execute')
def now():
print('20202020')

1
2
3
>>> now()
execute now():
20202020

注意:

经过装饰器装饰后的函数,它们的 __name__ 已经从原来的 'now’ 变成了 wrapper

要把原始函数的 __name__ 等属性复制到 wrapper() 函数中,需要使用,python 内置的 functools.wraps()

1
2
3
4
5
6
7
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kv):
print('call %s():' % func.__name__)
return func(*args, **kv)
return wrapper
CATALOG
  1. 1. Python_Advanced_Function_Programming
    1. 1.1. 高阶函数
    2. 1.2. 传入函数
    3. 1.3. map/reduce
  2. 2. Filter
    1. 2.1. 生成器
    2. 2.2. 迭代器
  3. 3. Sorted
  4. 4. 练习
  5. 5. 返回函数
  6. 6. 练习
  7. 7. 装饰器
    1. 7.0.1. 带参数的 decorator 带参数就需要编写一个返回 decorator 的高阶函数,提前接受需要的参数