Python学习笔记(六)之函数式编程
0. Python函数式编程思维导图
0.1 导图链接
0.2 导图截图
- 可能不够清晰看链接就好啦
1.函数式编程(Functional Programming)
- 在编程语言中(抽象程度是相对于计算机来说的):
- 越低级的语言,越贴近计算机,抽象程度越低,执行效率越高,如C语言;
- 越高级的语言,越贴近数学计算,抽象程度高,执行效率越低,如Lisp。
- 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。
- 函数式编程特点:
- 允许将一个函数作为参数传入到另一个函数中。
- 允许返回一个函数。
- Python对函数式编程提供部分支持,由于Python允许使用变量,所以它不是存函数式编程。
2. 高阶函数
2.1 高阶函数是什么?
-
首先了解两个概念
-
变量可以指向函数
-
例2.1.1:
>>> abs <built-in function abs> >>> a = abs >>> a(-1) 1
-
分析:函数本身可以赋值给变量。上述例子中被赋值的变量
a
可以当做abs
函数本身来使用。
-
-
函数名也是变量
-
例2.1.2:
>>> abs = 10 >>> abs(-1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable
-
分析:这里的函数名
abs
指向了数字10后,我们再以函数的形式调用它,就已经不起作用了,因为abs已经不指向求绝对值的函数,而是指向了10这个整数!说明函数名本身也是变量。
-
-
-
搞懂了上面两个概念后,我们再回过头来看高阶函数。
-
一个函数可以接受另一个函数作为参数,这种函数就是
高阶函数
。 -
例2.1.3:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" #一个计算两个列表总长度的高阶函数函数 def allLen(x, y, fun): return fun(x) + fun(y) if __name__ == "__main__": li1 = [1, 2, 3] li2 = [4, 5] print allLen(li1, li2, len)
-
输出
5
-
分析:上述高阶函数
allLen
是一个计算两个列表长度总和的函数。该函数将len
函数作为参数传入到allLen
函数中,所以是一个高阶函数。
-
2.2 map
-
基本形式:map(function, Iterable);
function
指某个特定的函数。Iterable
,即凡是可以使用for循环的对象都是Iterable类型的。如list, tuple, dict, set, str等。
-
map函数返回一个
Iterator
(即凡是可以使用next()函数的对象都是Iterator类型的)。- 注:因为
Iterator
是一个惰性序列,所以在返回值时可以使用list()
函数把整个序列编程一个list型。
- 注:因为
-
函数说明:map将传入的函数依次作用到序列的每一个元素,并把结果作为新的Iterator返回。
-
实例
-
例2.2.1:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" if __name__ == "__main__": li = [-1, -3, 2, -3] newList = list(map(abs, li)) print newList
-
输出:
[1, 3, 2, 3]
-
分析:上面例子中,map函数的作用是将abs函数作用于li列表的每一个元素,返回一个全部都是绝对值的新列表newList;这里在map函数外还嵌套了一个list函数的作用是将惰性序列转化为列表。
-
2.3 reduce
-
基本形式:reduce(function, Iterable);
function
指某个特定函数。Iterable
的说明如上。
-
reduce函数返回的是一个值。
-
函数说明:。
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,效果如下:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
-
实例
-
例2.3.1:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" def add(x, y): return x + y if __name__ == "__main__": li = range(11) sum = reduce(add, li) print sum
-
输出
55
-
分析:这里的reduce函数是将add函数作为功能函数,一开始传入两个li的元素,得到结果与li的下一个元素再传入add函数中,以此类推,得到最后的总和的结果。
-
2.4 filter
-
基本形式:filter(function, Iterable);
-
函数返回一个
Iterator
。 -
函数说明:
filter()
把传入的函数依次作用到每一个元素上,然后根据函数的返回值是True还是False决定保留还是丢弃该元素。即就像一个过滤器一样,过滤掉我们不想要的元素。 -
实例:
-
例2.4.1:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" #判断是否为偶数,偶数返回True,反之返回False def isEven(x): return x % 2 == 0 if __name__ == "__main__": li = [1, 2, 3, 4] newList = list(filter(isEven, li)) print(newList)
-
输出:
[2, 4]
-
分析:这里的isEven函数是一个功能函数,用来判断是否为偶数,再通过filter函数进行筛选,将li列表中不是偶数的元素筛选掉,左右输出的是只有偶数的序列。
-
2.5 sorted
-
sorted (iterable, *, key=None, reverse=False)
- iterable 即一个可以使用for循环的序列。
*
是一个特殊分隔符,后面的两个参数时命名关键字参数。key
中可以传入一个函数,用来指定排序规则,即映射函数,是排序的灵魂所在。reverse
则是决定最后输出序列是递增还是递减序列,默认是递增,即reverse = False
。想要改成递减即reverse = True
即可。
-
函数返回一个递增或递减序列。
-
函数说明:排序。
-
实例:
-
例2.5.1:
>>> li = ['Z', 'a', 'z', 'c', 'A'] >>> sorted(li) ['A', 'Z', 'a', 'c', 'z']
- 分析:默认情况下对字符串的排序是按照ASCII的大小进行排序的,其中大写字母
'Z'<'a'
,所以大写Z排在小写a之前。且默认情况下排序结果是递增序列。
- 分析:默认情况下对字符串的排序是按照ASCII的大小进行排序的,其中大写字母
-
改进2.0,例2.5.2:
>>> li = ['Z', 'a', 'z', 'c', 'A'] >>> sorted(li) ['A', 'Z', 'a', 'c', 'z'] >>> sorted(li, key = str.lower) ['a', 'A', 'c', 'Z', 'z']
- 分析:通过key关键字参数规定了排序规则为,将所有字母都变成小写字母后再进行排序,且最后得到的排序序列依旧是原来的字母。
-
改进3.0,例2.5.3:
>>> li = ['Z', 'a', 'z', 'c', 'A'] >>> sorted(li) ['A', 'Z', 'a', 'c', 'z'] >>> sorted(li, key = str.lower) ['a', 'A', 'c', 'Z', 'z'] >>> sorted(li, key = str.lower, reverse = True) ['Z', 'z', 'c', 'a', 'A']
- 分析:这次将reverse改成True,则函数根据key规则调整后,又将序列通过逆序输出了出来。
-
3. 返回函数
3.1 返回函数是什么?
-
顾名思义:函数作为返回值的函数。相当于函数中嵌套一个函数,然后实现该函数后,并返回该函数。
-
将函数作为结果返回也是高阶函数的另一个特性。
3.2 返回函数实例分析
-
例3.2.1:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum if __name__ == "__main__": f1 = lazy_sum(1, 2, 3, 4) f2 = lazy_sum(5, 6, 7, 8) print f1, f2 print f1(), f2()
-
输出:
<function sum at 0x0000000002F785F8> <function sum at 0x0000000002F78668>
10 26 -
分析:
- 可以看到,当我们调用f1和f2的时候,得到的是一个函数本身,即lazy_sum内部函数sum。
- 当我们想要得到内部函数sum所求的值时,我们需要调用
f1()
和f2()
来得到。 - 值得注意的是,这里在函数
lazy_sum
中又定义了函数sum
,且内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量。当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。
4. 匿名函数(lambda)
4.1 匿名函数是什么?
- 所谓匿名,匿名,就是将名字隐藏,或则直接说它是没有名字的函数也不为过。哈哈,Python的匿名函数可以说所有的匿名函数的名字统一都是
lambda
。 - 匿名函数的存在,将已经非常简约的函数又一次变得更简约,且在某些函数使用次数不多的情况下,可以随心所欲的创建和使用。
- 相对于java来说,Python只对匿名函数提供了有限的支持。因为Python中的匿名函数只能够有一个表达式。
4.2 匿名函数实例分析
-
例4.2.1:
>>> list(map(lambda x : 2 * x, [1, 2, 3, 4, 5])) [2, 4, 6, 8, 10]
-
分析:其中map函数和list的转化已经比较熟悉了。那么来看一下这里的
lambda
函数lambda x : 2 * x
,它就相当于一下的函数:def f(x): return 2 * x
-
其中
lambda
函数中的关键字lambda
就是用来昭示匿名函数,冒号前面的x
表示要传入函数的参数,冒号后的2 * x
表示的就是return
的内容。
-
-
例4.2.2
>>> f = lambda x, y : x + y >>> f(1, 2) 3 >>> f('a', 'b') 'ab'
-
分析:如该
lambda
函数表达式所示,函数传入两个数据,中间用,
号隔开,并返回两个数据的和,当然和也可以是字符串的和。且在该例子中,我们将该匿名函数作为一个对象赋值给了变量f
,调用变量f
即可调用我们创建的匿名函数。该函数的原型应该如下:def f(x, y): return x + y
-
4.3 匿名函数注意点
- 好处:
- 因为匿名函数没有名字,所以不必担心函数名冲突的问题。
- 匿名函数也是一个函数对象,可以赋值给一个变量,再利用变量来调用函数。
- 需要注意的是,匿名函数有一个限制,就是只能有一个表达式,该表达式的计算结果就是该匿名函数的返回结果。所以只有一些简单的情况下可以使用匿名函数。
5. 装饰器(Decorator)
5.1 装饰器是什么?
-
首先来写一个简单的函数:
def now(): print("这里是now函数,时间是2020年1月19日21:57:19。")
-
现在我们有了一个简单的
now()
函数,当我们想在这个函数的前后加一些装饰,即给他加一些说明时,我们往往会在now函数里面动手脚,但是当我们想要在不动now函数本体的情况下进行一些修改时,我们就要用到装饰器了。 -
这种在代码运行期间动态增加功能的方式,称之为
装饰器
。
5.2 装饰器实例分析
-
实例1:两层嵌套decorator用法
-
例5.2.1:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" #用作now()函数的装饰函数 def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) print("现在执行的是wrapper函数") return wrapper @log def now(): print("2020 1 19") if __name__ == "__main__": now()
-
输出:
现在执行的是wrapper函数
call now():
2020 1 19 -
分析:在调用now函数的时候,实际上我们先调用了
@log
中的函数内容。Python就是通过@
语法,把decorator置于函数的定义处的,并在调用该函数的时候,实际上是执行了now = log(now)
。
之所以我们看到先执行了wrapper函数,是因为我们进入到log函数后,先跳过了wrapper函数,执行了print("现在执行的是wrapper函数")
,在返回函数wrapper的时候调用了wrapper函数,并输出了下面的内容。我们看到wrapper函数的参数是(*args, **kw)
,所以该函数可以接受任意参数的调用。
-
-
实例2:三层嵌套decorator用法
-
实例5.2.2:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" #用作now()函数的装饰函数 def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) print('我现在执行的是decorator函数') return wrapper print("我现在执行的还是log函数") return decorator @log('execute') def now(): print("2020 1 19") if __name__ == "__main__": now()
-
输出:
我现在执行的还是log函数
我现在执行的是decorator函数
execute now():
2020 1 19 -
分析:3层decorator调用实际上效果是:
now = log('execute')(now)
。首先我们看到执行的是log函数,因为log函数要传参进去,我们传了一个'execute'信息进去,首先我们跳过里面的两层嵌套循环来到了print("我现在执行的还是log函数")
语句,在要返回decorator函数时我们进入到decorator函数,其中的func参数就是now
函数了,这时我们又先跳过了wrapper函数执行了print('我现在执行的是decorator函数')
语句,在返回wrapper函数的时候进入到wrapper函数,执行了wrapper函数的print('%s %s():' % (text, func.__name__))
语句,并进入func函数,即执行了now函数中的内容,最后层层返回,先返回到decorator函数,再最后退出了log装饰器。
-
6. 偏函数(Partial function)
6.1 老规矩,偏函数是什么?
- 就是当函数的参数太多的时候,或则我们需要在某些时候经常改变某些参数的时候,我们需要简化,这时候可以使用(functools.partial)创建一个新的函数,这个函数可以固定住原来的参数的部分参数,从而使我们调用的时候更加的简单。
6.2 话不多说,直接上实例
-
例6.2.1:这里以
int()
函数为例,当我们需要转换大量的二进制字符串时,每次传入int(x, base = 2)
非常麻烦,这时可以用下面方法解决。#!/user/bin/python #coding=utf-8 _author_ = "zjw" import functools if __name__ == "__main__": int2 = functools.partial(int, base = 2) print(int2('101'))
-
输出:
5
-
分析:其中
int2 = functools.partial(int, base = 2)
语句其实可以用下面的函数代替:def int2(x, base = 2): return int(x, base)
两个的实现方式不同,但是能用一行代码解决的我们坚决不用两行。但是呢这只是暂时固定住函数的base参数,如果我们更改base值,调用时也是会发生变化的,如下:
#!/user/bin/python #coding=utf-8 _author_ = "zjw" import functools if __name__ == "__main__": int2 = functools.partial(int, base = 2) print(int2('101', base = 8))
此时输出的是65了,即八进制转为十进制。
6.3 偏函数小总结
- 当函数参数个数太多,我们需要简化是,我们不妨用上偏函数,使用
functools.partial
来创建一个新的函数,来固定住原函数中的某些参数,实现新的通用的功能。且用该创建方法更加的简洁,实则就是’节约代码行数‘啦。