④Python函数式编程_python

④Python函数式编程


函数式编程的一个特点是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

高阶函数

高阶函数有如下几个特征,逐步深入理解。

  • 变量可以指向函数
>>> abs(-10)
10
>>> abs
<built-function abs>
>>> x=abs(-10)
>>> x
10
>>> f = abs
>>> f(-10)
10

如果把函数本身赋值给变量,即便相知相函数,那么可以通过变量来调用函数。

  • 函数名也是变量
    这里仅仅说明,函数名可以当作变量使用,但是实际编程过程中,基本不会将函数名当成变量来使用。
>>> abs=10
>>> abs
10
  • 传入函数
    变量可以指向函数,函数的参数可以接受变量,那么自然而然一个函数也能接受另一个函数作为参数,这种函数就称之为高阶函数
def add(x,y,f):
return f(x)+f(y)

上面定义的函数可以通过​​add(-5,6,abs)​​这种方式调用。

map

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

举例说明:

>>> def square_func(x):
return x*x

>>> r=map(square_func,[1,2,3,4,5])
>>> list(r)
[1,4,9,16,25]

​map​​​返回的是惰性序列,因此通过​​list()​​函数把整个序列都计算出来并返回一个list。

实际上​​map()​​作为高阶函数,将运算规则抽象了,因此,我们不但可以计算简单的求平方的函数,还可以计算任意复杂的函数。例如将一个list所有数字都转化为字符串。

>>> list(map(str,[1,2,3,4,5,6,7,8,9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

reduce

​reduce​​​把一个函数作用在一个序列上,这个函数必须接受两个参数,​​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

r = reduce(add,[1,2,3,4])
print(r)#result:10

map与reduce结合使用

map可以与reduce实现更多的功能,甚至可以和lambda表达式结合使用。

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))

上面的代码就可以实现将字符串转化为整数的功能。

filter

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

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

def is_odd(n)
return n % 2 ==1

sorted

排序是程序中经常会使用的功能,无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,可以直接比较,但是如果是字符串和字典,直接比较数学上的大小是没有意义的,此时必须通过函数抽象出来。

大家所熟知的​​sorted()​​​函数其实也是个高阶函数,可以接受一个​​key​​函数来实现自定义的排序。

>>> sorted([36,5,-12,9,-21],key=abs)
[5,9,-12,-21,36]

key指定的函数会作用在list的每个元素上,并根据key函数返回的结果进行排序。

高阶函数的抽象能力非常强,而且,核心代码可以保持的非常简洁。

返回函数

函数作为返回值,高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

可以通过返回凡是来定义一个求和函数:

def lazy_sum(*args):
def sum():
ax=0
for n in args:
ax = ax + n
return ax
return sum
>>> f =lazy_sum(*args)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()#调用函数,产生结果

当​​lazy_sum​​​函数返回函数​​sum​​时,相关参数和变量都保存在返回的函数中,这种称为“闭包”(Closure)的程序结构拥有极大的威力。

​lazy_sum​​函数的每次调用都会返回一个新的函数,即使传入相同的参数。

  • 闭包
    返回闭包时注意一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
    比如下面这种情况就是错误的:
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()

本以为会返回1,4,9但是实际上会返回9,9,9。这是因为等到3个函数都返回时,他们所引用的变量​​i​​​已经变成​​3​​​,因此最终结果为​​9​​。

为了解决这个问题,可以再创建一个函数,用该函数的参数绑定循环变量当前的值。

def count():
def f(j):
def g():
return j*j
return g
fs=[]
for i in range(1,4):
fs.append(f(i))
return

匿名函数

也就是常说的​​lambda​​表达式,一定程度上,匿名函数可以简化代码的书写,但是也会增加代码的理解难度。

list(map(lambda x:x*x,[1,2,3,4,5,6,7,8,9]))

​lambda​​表示匿名函数。

匿名函数没有函数名,不需要担心函数冲突,同时匿名函数是一个函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数。

装饰器

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

def now():
print('5/14/2020')
>>> f=now
>>> f()

函数对象有一个​​__name__​​属性,可以拿到函数名字。

>>> now.__name__
now

假设现在,要增强​​now()​​​函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改​​now()​​函数定义,这种在代码运行期间动态增加功能的方法,称之为“装饰器”。

def log(func):
def wrapper(*args,**kw):
print('call %s()'%func.__name__)
return func(*args,**kw)
return

上面的​​log​​,因为是一个decorator,所以接受一个函数作为参数,并返回一个函数。借助Python的@羽凡,把decorator置于函数的定义处:

@log
def now():
print('5/14/2020')
>>> now()
call now():
5/14/2020

把​​@log​​​放到​​now()​​函数的定义处,相当于执行了语句:

now=log(now)

由于​​log()​​​是一个decorator,返回一个函数,所以,原来的​​now()​​​函数仍然存在,只是现在同名的​​now​​​变量指向了新的函数,于是调用​​now()​​​将执行新函数,即在​​log()​​​函数中返回的​​wrapper()​​函数。

​wrapper()​​​函数的参数定义是​​(*args, **kw)​​​,因此,​​wrapper()​​​函数可以接受任意参数的调用。在​​wrapper()​​函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更加复杂。

def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return

这个三层嵌套的decorator用法如下

@log('execute')
def now():
print('2015-3-25')

执行结果如下

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行​​log('execute')​​​,返回的是​​decorator​​​函数,再调用返回的函数,参数是​​now​​​函数,返回值最终是​​wrapper​​函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有​​__name__​​​等属性,但你去看经过decorator装饰之后的函数,它们的​​__name__​​​已经从原来的​​'now'​​​变成了​​'wrapper'​​:

>>> now.__name__
'wrapper'

因为返回的那个​​wrapper()​​​函数名字就是​​'wrapper'​​​,所以,需要把原始函数的​​__name__​​​等属性复制到​​wrapper()​​函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写​​wrapper.__name__ = func.__name__​​​这样的代码,Python内置的​​functools.wraps​​就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return

或者针对带参数的decorator:

import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return

​import functools​​​是导入​​functools​​​模块。模块的概念稍候讲解。现在,只需记住在定义​​wrapper()​​​的前面加上​​@functools.wraps(func)​​即可。

偏函数

​functolls.partial​​就是帮助我们创建一个偏函数,不需要我们自己定义函数,可以直接使用用偏函数定义的函数。

import functools
int2 = functools.partial(int,base=2)#按照二进制进行转换,转换成int数字