Lipsky

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 的高阶函数,提前接受需要的参数#