学习资源:廖雪峰Python教程
重点记录与c,java有区别的知识点,红色部分重点注意。
四、函数式编程
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。由于Python允许使用变量,因此Python不是纯函数式编程语言
1.高阶函数(Higher-order function)
(1)什么是高阶函数?
①变量可以指向函数:
f = abs #abs为内置的绝对值函数,将这个函数名赋给一个变量
print(f(-10)) #输出10,说明变量f指向了绝对值函数abs
②函数名也是变量:
对于abs()这个函数,可以把函数名abs看作一个变量,它指向一个绝对值函数:
abs = 10 #将整数10赋给abs
print(abs) #输出10
print(abs(-10)) #报错,此时abs不再指向绝对值函数,而是一个整数10
③函数能够以函数作为参数(传入函数):
def add(x, y, f):
return f(x) + f(y)
x, y = -2, 3
f = abs
result = add(x, y, f) #将abs()函数作为第三个参数传入add()函数
print(result) #输出5,说明abs()成功作为一个参数传入了函数add()
④把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
(2)map/reduce
①map():map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
def f(x):
return x * x;
r = map(f, list(range(1, 9))) #将第一个参数指向的函数对象,依次作用于第二个参数指向的序列中的每一个元素
print(list(r)) #输出[1, 4, 9, 16, 25, 36, 49, 64] #map返回的是一个Iterator,是惰性序列,list()函数将这个Iterator计算成一个list
#输出['1', '2', '3', '4', '5', '6', '7', '8'],即将1-9的整数转化为1-9的字符串
print(list(map(str, list(range(1, 9)))))
②reduce():reduce()函数把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
#这个求和例子清晰说明reduce()函数的过程
from functools import reduce
def add(x, y):
return x + y
result = reduce(add, list(range(1, 5))) #将1,2,3,4相加
print(result) #输出10
③map()配合reduce()实现str转化为int的函数:
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)) #map()函数将字符串s依次转化为单个数字1,2,3,4,5
#reduce再将fn()函数作为第一个参数,1,2,3,4,5作为第二个参数进行计算
result = str2int('12345')
print(result) #输出12345整数
(3)filter
①filter()函数用于过滤序列,filter()也接收一个函数和一个序列,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素,filter()函数返回的是一个Iterator:
def is_odd(n):
return n % 2 == 1
result = list(filter(is_odd, [1, 2, 3, 4, 5, 6])) #利用filter()函数筛选奇数
print(result) #输出[1, 3, 5]
②利用filter实现埃及筛法求素数
#这是一个生成无限序列的生成器,构造一个从3开始的奇数序列:3,5,7,...
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
def _not_divisible(n):
return lambda x : x % n > 0
#一个不断返回下一个素数的生成器
#这个函数中的it从初始序列3,5,7,9,11,13...经过filter函数的筛选,不断获得新序列
#5,7,11,13,17,... 不能被3整除的序列
#7,11,13,17,19,... 不能被5整除的序列
#11,13,17,19,23,... 不能被7整除的序列
def primes():
yield 2 #2也是素数,先返回一个2
it = _odd_iter() #获得序列3,5,7,...
while True:
n = next(it) #返回当前序列的第一个数
yield n
it = filter(_not_divisible(n), it) #构造新序列
for n in primes():
if n < 100:
print(n)
else:
break
(4)sorted
sorted()是Python内置的排序函数:
L = [8, 5, -1, 9, -4]
result1 = sorted(L)
result2 = sorted(L, reverse = True) #reverse参数将排序结果反序
result3 = sorted(L, key = abs) #接收一个key实现自定义排序
#key先将指定的函数将作用于list的每一个元素上,再key函数返回的结果序列进行排序
#按元素的绝对值排序
print(result1) #输出排序后的结果[-4, -1, 5, 8, 9]
print(result2) #输出排序后的结果[9, 8, 5, -1, -4]
print(result3) #输出排序后的结果[-1, -4, 5, 8, 9]
2.返回函数
①函数作为返回值:高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回:
def lazy_sum(*args):
def sum(): #内部函数sum()可以引用外部函数lazy_sum的参数和局部变量
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 2, 3, 4) #此时的f指向一个函数对象sum(1, 2, 3, 4)
result = f() #再次调用函数f()
print(result) #输出求和结果10
f1 = lazy_sum(1, 2, 3, 4)
f2 = lazy_sum(1, 2, 3, 4)
print(f1 == f2) #输出False,因为每次都会返回一个新的函数 ②
②闭包:如果在一个函数的内部定义了另一个函数,外部的称为外函数,内部的称为内函数。在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
返回闭包时牢记一点:返回的内函数中不要引用任何循环变量,或者后续会发生变化的变量:
def count():
fs = []
for i in range(1, 4):
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
print(f1(), f2(), f3()) #输出9 9 9,并非预想中的1 4 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
f1, f2, f3 = count()
print(f1(), f2(), f3())
3.匿名函数
L = list(map(lambda x : x * x, list(range(1,9))))
print(L) #输出[1, 4, 9, 16, 25, 36, 49, 64]
关键字lambda表示匿名函数,冒号前面的x表示函数参数
限制:只能有一个表达式,不写return,返回值就是该表达式的结果
好处:不用担心函数名冲突,可以赋给一个变量,也可以作为返回值
4.装饰器(Decorator)
①装饰器本质上是一个高级函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象:
#log()是一个装饰器,接受一个函数作为参数并返回一个函数
def log(func):
def wrapper(*args, **kw): #wrapper()可以接受任意参数的调用
print('call %s():' % func.__name__) #函数对象有一个__name__属性,可以拿到函数的名字
return func(*args, **kw)
return wrapper
@log #用@将装饰器log()置于函数now()的定义处,相当于执行了语句 now = log(now)
def now():
print('2018-2-8 15:46')
now() #输出call now():
# 2018-2-8 15:46
#在运行now()之前,先运行装饰器log()
在wrapper内部,首先打印,然后接着调用原始函数
②带参数的装饰器:
#带参数的装饰器
def log(text):
def decorate(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorate
@log('excute')
def now():
print('2018-2-8 15:57')
now()
运行now()时,相当于执行log('excute')(now)。这个语句首先执行log('excute')获得指向decorate()函数对象的返回值。再执行decorate(now),最终返回的是wrapper()函数对象
③上述经过装饰器装饰后的函数的__name__属性已经变为‘wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中。一个完整的装饰器写法如下:
import functools
#log()是一个不带参数的装饰器,接受一个函数作为参数并返回一个函数
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw): #wrapper()可以接受任意参数的调用
print('call %s():' % func.__name__) #函数对象有一个__name__属性,可以拿到函数的名字
return func(*args, **kw)
return wrapper
@log #用@将装饰器log()置于函数now()的定义处,相当于执行了语句 now = log(now)
def now():
print('2018-2-8 15:46')
now() #输出call now():
# 2018-2-8 15:46
#在运行now()之前,先运行装饰器log()
或者带参数的装饰器:
#带参数的装饰器
import functools
def log(text):
def decorate(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorate
@log('excute')
def now():
print('2018-2-8 15:57')
now()
5.偏函数
偏函数的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
import functools
def add(x, y):
return x + y
add2 = functools.partial(add, y = 2) #将add()函数的y参数设置默认值为2,构成函数add2()
result = add2(3) #3 + 2
print(result) #输出5
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。