过把大段代码拆成函数,通过层层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。 而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
高阶函数英文叫Higher-order function
一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数 如:
abs(-10)
# 结果:10
x = abs
# 把函数本身赋值给变量,换句话说就是变量可以只想函数
x(-10)
# 结果:10
函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
x = abs # 将x变量指向abs内置函数
print(x(-2)) # 调用x函数其实就是调用abs函数
def add(x, y, f):
return f(x) + f(y)
>>> add(-5, 6, abs)
11
map()
函数接收两个参数,第一个是函数名,第二个是 Iterable
类型的参数,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的迭代器 Iterator
返回。
如有一个函数f(x):x*x,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上:
d = [1, 2, 3, 4, 5, 6, 7, 8, 9]
def pf(num):
return num * num
print(map(pf,d))
与map
很类似,同样是把一个函数作用在一个序列 [x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算
其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
如果要把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场:
from functools import reduce
def fn(x, y):
return x * 10 + y
...
reduce(fn, [1, 3, 5, 7, 9])
# 输出:13579
还可以写一个str2int的函数:
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 x, y: x * 10 + y
#相当于
def method(x ,y):
return x * 10 +y
是一个用于过滤序列的高阶函数 和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
排序的核心是比较两个元素的大小。 如果是数字可以直接比较,但如果是字符串或两个dict呢? 直接比较数学上的大小是没有意义的(如数字),因此,比较的过程必须通过函数抽象出来。
内置的sorted()函数可以对list类型的参数进行排序:
sorted([36, 5, -12, 9, -21])
# 输出:[-21, -12, 5, 9, 36]
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序: 注意:key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
sorted([36, 5, -12, 9, -21], key=abs)
# 输出:[5, 9, -12, -21, 36]
对字符串排序,是按照ASCII的大小比较的
sorted(['bob', 'about', 'Zoo', 'Credit'])
# 输出:['Credit', 'Zoo', 'about', 'bob']
反向排序,不必改动key函数,可以传入第三个参数reverse=True
:
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
# 输出:['Zoo', 'Credit', 'bob', 'about']
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
f
# 结果:<function lazy_sum.<locals>.sum at 0x101c6ed90>
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。
注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
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()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
执行每一个函数时返回的全部都是9
,请注意分析!
说白了就是lambda
如匿名函数lambda x: x * x
实际上就是:
def f(x):
return x * x
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def now():
print('2015-3-25')
...
f = now
f()
# 返回:2015-3-25
函数对象有一个__name__属性,可以拿到函数的名字:
now.__name__
# 结果:'now'
f.__name__
# 结果:'now'
假设现在要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
now()
# 输出:call now():
# 输出:2015-3-25
把@log放到now()函数的定义处,相当于在这位置执行了语句:
now = log(now)
整个完整的代码:
def log(func):
def wrapper(*args, **kw):
print('%s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
now()
提高难度
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()
在@log('execute')
的位置处相当于执行了 log('execute')(now)
;
通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
int('12345')
12345
但int()函数还提供额外的base参数,默认值为10。如果传入base
参数,就可以做N进制的转换:
int('12345', base=8)
5349
int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
64
int2('1010101')
85