Python_Advanced_Function_Programming
通过大段代码拆成函数,通过一层一层的函数调用,可以把复杂的任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程,可以归结到面向过程的程序设计,但其思想更接近数学计算。
越低级的语言,越接近计算机,抽象程度低,执行效率高,如 C 语言;越高级的语言,越接近计算,抽象程度高,执行效率低,如 Lisp 语言。
纯函数式编程:
函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数称之为没有副作用
非纯函数式编程:
允许使用变量的程序设计语言,由于函数内部变量状态不确定,同样的输入,可能得到不同的输出,因此这种函数是有副作用的。
函数式编程的特点:
- 允许把函数本身作为参数传入另一个函数
- 允许返回一个函数
Python 允许使用变量,因此,Python 不是纯函数式编程语言。
高阶函数
eg:
1 | f = abs |
函数本身也可以赋值给变量,即:变量可以指向函数
1 | f = abs |
传入函数
一个函数可以接受另一个函数作为参数,这种函数称之为高阶函数
eg.
1 | def add(x, y, f): |
map/reduce
map
map() 函数接受两个参数,一个是函数,一个是 Iterable,map 将传入的函数一次作用到序列的每个元素,并把结果作为新的 Iterable 返回
eg.
1 | def f(x): |
1 | 1, 2, 3, 4, 5, 6, 7, 8, 9])) list(map(str, [ |
reduce
reduce 把一个函数作用在一个序列 [x1, x2, x3, …] 上,这个函数必须接受两个参数,reduce 把结果继续和序列的下一个元素做累积计算
比如对一个序列求和,可以用 reduce 实现:
1 | from functools import reduce |
python 内建有 sum() 函数,不必动用 reduce
如果要把序列 [1, 3, 5, 7, 9] 变换成整数 13579, reduce 就可以派上用场:
1 | from functools import reduce |
考虑到字符串 str 是一个序列,配合 map( ) ,可以写出把 str 转换成 int 的函数
1 | from functools import reduce |
整理为函数就是:
1 | from functools import reduce |
用 lambda 进一步简化为:
1 | from functools import reduce |
lambda 使用方法
即匿名函数
eg
1 | map(lambda x: x * x, [y for y in range(10)] |
练习
- 利用
map()
函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT']
,输出:['Adam', 'Lisa', 'Bart']
:
1 | def FirstCap(s): |
- Python提供的
sum()
函数可以接受一个list并求和,请编写一个prod()
函数,可以接受一个list并利用reduce()
求积:
1 | def Multi(L): |
- 利用
map
和reduce
编写一个str2float
函数,把字符串'123.456'
转换成浮点数123.456
:
1 | DIGITS ={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9} |
Filter
Python
内建的 filter()
函数用户过滤序列
eg:在一个list中删掉偶数,只保留奇数
1 | def is_odd(n): |
Filter 筛选素数
构造一个从3开始的奇数序列
1 | def _odd_iter(): |
生成器
1 | for x in range(10)] L = [x * x |
**L
和 g
的区别仅在于最外层的 []
和 ()
,L
是一个 list
,而 g
是一个 generator
**
如果想要一个一个打印出来,可以通过next()
函数获得 generator
的下一个返回值:
1 | next(g) |
斐波那契数列(Fibonacci)
1 | # 函数版本 |
注意
1 | a, b = b, a + b |
相当于:
1 | t = (b, a + b) # t is a tuple |
所以会有:
1 | 6) fib( |
把上面的函数 fib
变成 generator
, 只需要把 print(b)
改成 yield b
1 | def fib(max): |
此时这个函数就不再是一个普通函数,而是一个 generator
1 | 6) f = fib( |
注意:
函数时顺序执行的,遇到 return
或者最后一行函数就返回。
generator
在每次调用 next()
时候执行,遇到 yield
语句返回,再次执行时从上次返回的 yield
语句处继续执行
当函数变为 generator
后,一般不用 next()
来获取下一个返回值,而是直接使用 for
循环来迭代:
1 | for n in fib(6) |
但是使用 for
循环不会拿到 generator
的 return
语句的返回值,必须使用 StopIteration
捕获错误,返回值包含在 StopIteration
的 value
中:
1 | 6) g = fib( |
杨辉三角
1 | 1 |
1 | def triangles(n): |
迭代器
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python的for
循环本质上就是通过不断调用next()
函数实现的,例如:
1 | for x in [1, 2, 3, 4, 5]: |
实际上完全等价于:
1 | # 首先获得Iterator对象: |
继续 Filer() 求素数
定义一个筛选函数:
1 | def _not_divisible(n): |
定义一个生成器,不断返回下一个素数:
1 | def primes(): |
由于,primes() 也是一个无线序列,所以调用的时候需要设置一个退出循环的条件:
1 | for n in primes(): |
练习
利用filter
判断一个数是否是回数:
回数是指从左向右读和从右向左读都是一样的数,例如
123321
,99
1 | def _is_palindrome(s): |
Sorted
sort 与 sorted 区别
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
Python 内置的 sorted()
函数可以对 list
进行排序:
1 | 38,2,23,2,1]) sorted([ |
此外,sorted() 函数也是一个高阶函数,它还可以通过接受一个 key 函数实现自定义的排序,如按绝对值大小进行排序:
1 | 31,-333,13,1],key = abs) sorted([ |
对于字符串排序:
默认情况下,对字符串排序,是按照 ASCII
的大小比较的,由于 Z
< a
,结果 Z
字母会排在小写字母 a
的前面
要实现,按照字母顺序排序,只需要对 key 函数改为忽略大小写排序
1 | 'bob', 'about', 'Zoo', 'Credit'], key=str.lower) sorted([ |
要进行反向排序,只需要传入第三个参数 reverse = True
1 | 'bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) sorted([ |
练习
假设我们用一组tuple表示学生名字和成绩:
1 | L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] |
请用sorted()
对上述列表分别按名字排序:
再按成绩从高到低排序:
1 | L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] |
1 | print(sorted(L,key = by_name)) |
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
1 | def lazy_sum(*args): |
1 | 1, 3, 5, 7, 9) f = lazy_sum( |
调用函数 f
时,才真正计算求和的结果:
1 | f() |
在函数 lazy_sum中,定义了函数 sum,并且,内部函数 sum 可以引用外部函数的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为 “闭包(Closure)”。
闭包:
1 | def count(): |
1 | f1() |
全部都是9的原因,在于返回的函数引用了变量 i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量 i, 已经变成了 3,因此最终结果是 9。
返回闭包时,牢记:返回函数不要引用任何循环变量,或者后续发生变化的变量。
如果一定想要引用循环变量,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
1 | def count(): |
结果:
1 | f1, f2, f3 = count() |
练习
利用闭包返回一个计数器函数,每次调用它返回递增整数:
1 | def createCounter(): |
装饰器
函数是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
1 | def now(): |
现在,假设我们要增强 now() 函数的功能,而又不希望修改 now() 函数的定义,这种在代码运行期间动态增加功能的方式,称之为 “ 装饰器“(Decorator)。
本质上,decorator 就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的 decorator:
1 | def log(func): |
注意:
wrapper
中的 *args, **kw
参数定义,目的是让 wrapper()
函数可以接受任意参数的调用。
带参数的 decorator
带参数就需要编写一个返回 decorator
的高阶函数,提前接受需要的参数
1 | def log(text): |
1 | now() |
注意:
经过装饰器装饰后的函数,它们的 __name__
已经从原来的 'now’
变成了 wrapper
要把原始函数的 __name__
等属性复制到 wrapper() 函数中,需要使用,python 内置的 functools.wraps()
1 | import functools |