前言:在Python学习中需要知道的只有Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
4.1 高阶函数(Higher-order function)
高阶函数有几个特点:
- 变量可以指向函数
- 函数名也是变量
- 函数可作为参数传入
map/reduce
map()
和reduce()
是Python中内建的两个函数。
首先看map()
函数,它接收两个参数,一个是函数,一个是序列,将传入的函数依次作用到序列的每个元素,并把结果作为新的list
返回。
>>> map(abs,(-1,2,-3,4)) #其实这些都是为了方便而生
[1, 2, 3, 4]
而reduce()
函数也接受两个参数,一个函数一个序列,也是把函数作用在序列上,该函数必须接收两个参数!但它是把结果和序列的下一个元素作累积运算,效果如下:
reduce(f, [a,b,c])=f(f(f(a),b),c)
两个函数配合使用,可以将str
转化为int
,如下:
>>> def chrtonum(s):
... return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]#将字符串看作序列,集合的元素索引l[i]
>>> def strtoint(s):
... return reduce(lambda x,y:x*10+y,map(chrtonum,s))#使用了匿名函数
>>> strtoint('987')
987
再举一个例子,用map()
函数把用户输入的不规范的英文名字,改成首个字母大写,其余小写的规范格式:
>>> def my_lower(s):
... return s.lower()
>>> def normalize(s):
... s = map(my_lower,s)
... i = 0
... l = []
... while i < len(s):
... l.append(s[i][0].upper() + s[i][1:])
... i = i +1
... return l
>>> m = ['sA','apple','bERS']
>>> normalize(m)
['Sa', 'Apple', 'Bers']#太复杂了
#换一种方法,第一种太冗杂,也没有利用map的精髓,况且我还不知道有title()这个method = =
>>> def normalize(s):
... return s.title()
>>> print map(normalize,['adam','LISA','barT'])
['Adam', 'Lisa', 'Bart']
利用reduce()
函数编写一个求积函数:
>>> def prod(s = []):
... return reduce(lambda x,y:x*y,s) #再次用到了匿名函数
>>> prod([1,2,3,4,5])
120
filter
Python内建的filter()
用于过滤序列,接收一个函数和一个序列,然后依次作用于每个序列的元素根据返回值是True
还是False
进行保留与删除。例如删掉1~100的素数:
>>> def no_prime(s):
... if s==1:
... return True
... if s==2:
... return False
... for i in range(2,s):
... if s%i==0:
... return True
... return False
>>> filter(no_prime,range(1,101))
[1,4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46,48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 72, 74, 75, 76, 77, 78, 80, 81, 82, 84, 85,86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 98, 99, 100]
sorted
排序算法一直都是程序中比较热门的话题,对于不是数字的比较,应该会直接比较ascii
码,通常规定,对于两个元素x和y,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1。
Python中内建的sorted()
可以直接对list进行排序,也可以接收一个比较函数来实现自定义的排序。
例如编写一个倒序的函数:
>>> def reverse(x,y):
... if x > y:
... return -1
... if x <y:
... return 1
... return 0
>>> sorted(reverse,[4,3,5,7])#注意顺序,函数放在最后
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'function' object is not iterable
>>> sorted([4,3,5,7],reverse)
[7, 5, 4, 3]
4.2 返回函数
顾名思义,将函数作为结果返回。而在此处,涉及到了闭包(Closure)的概念。
>>> 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()
25
>>> f
<function sum at 0x02A1C730>
在函数lazy_sum
中又定义了函数sum,内部函数sum可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数。
>>>def count(): #Python的函数只有在执行时才会去找函数体变量的值
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()
9
#原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。我理解为取最后循环
对于闭包,可以理解为闭包=函数块+定义函数时的环境,返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
有一段经典的错误代码:
>>> def test():
... a = 1
... def bar():
... a = a +1
... return a
... return bar
...
>>> f = test()
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in bar
UnboundLocalError: local variable 'a' referenced before assignment
这是因为Python在执行代码时规定将闭包函数体内的赋值语句左边的变量统一当作局部变量,所以最后在调用时编译器会在函数体内寻找a的初始值,找不到就报错,而且闭包中是不会修改外部作用域的变量的。
4.3 匿名函数
匿名函数的格式为lambda x:x*x
- 关键字lambda表示匿名函数,冒号前面的x表示函数参数。
- 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
- 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
4.4 装饰器(Decorator)
在代码运行期间动态增加功能的方式,称之为Decorator
,本质上就是一个返回函数的高阶函数。
>>> def log(func): #这是一个能打印日志的decorator
... def wrapper(*args, **kw):
... print 'call %s():' % func.__name__ #__name__属性可以拿到函数的名字
... return func(*args, **kw) #意味着wrapper能接受任意参数
... return wrapper
...
>>> @log #相当于log(now)
... def now():
... print '2016-05-24'
...
>>> now()
call now():
2016-05-24
如果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('test')
... def now():
... print '2016-05-24'
...
>>> now()
test now():
2016-05-24
这里面函数名称发生了变化,即__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 wrapper
...
>>> @log
... def now():
... print '2016-05-24'
...
>>> now()
call now():
2016-05-24
>>> now.__name__
'now'
具体操作就是导入functools
模块后,在定义wrapper函数前加入@functools.wraps(func)
即可。
4.5 偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function),使用时要import functools。
>>> import functools
>>> int2 = functools.partial(int,base = 2)
>>> int2('100')
4
简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。