python学习笔记---函数式编程【廖雪峰】
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
函数式编程
函数就是面向过程的程序设计的基本单元。
函数式编程的一个特点就是允许把函数本身作为参数传入另一个函数还允许返回一个函数
Python对函数式编程提供部分支持。由于Python允许使用变量因此Python不是纯函数式编程语言。
高阶函数
结论函数本身也可以赋值给变量即变量可以指向函数。
如果一个变量指向了一个函数那么可以通过该变量来调用这个函数
>>> f = abs
>>> f(-10)
10
函数名也是变量
那么函数名是什么呢函数名其实就是指向函数的变量对于abs()
这个函数完全可以把函数名abs
看成变量它指向一个可以计算绝对值的函数
既然变量可以指向函数函数的参数能接收变量那么一个函数就可以接收另一个函数作为参数这种函数就称之为高阶函数。
def add(x, y, f):
return f(x) + f(y)
map()
map()
函数接收两个参数一个是函数一个是Iterable
map
将传入的函数依次作用到序列的每个元素并把结果作为新的Iterator
返回。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
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
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
filter()
filter()
函数用于过滤序列和
map()
类似filter()
也接收一个函数和一个序列。和map()
不同的是filter()
把传入的函数依次作用于每个元素然后根据返回值是True
还是False
决定保留还是丢弃该元素。----既实现过滤的功能
把一个序列中的空字符串删掉可以这么写
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
注意到filter()
函数返回的是一个Iterator
也就是一个惰性序列所以要强迫filter()
完成计算结果需要用list()
函数获得所有结果并返回list。
sorted()
排序算法
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序排序的核心是比较两个元素的大小。如果是数字我们可以直接比较但如果是字符串或者两个dict呢直接比较数学上的大小是没有意义的因此比较的过程必须通过函数抽象出来。
①数字排序
Python内置的sorted()
函数就可以对list进行排序
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
此外sorted()
函数也是一个高阶函数它还可以接收一个key
函数来实现自定义的排序例如按绝对值大小排序
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
②字符串排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
默认情况下对字符串排序是按照ASCII的大小比较的由于'Z' < 'a'
结果大写字母Z
会排在小写字母a
的前面。
现在我们提出排序应该忽略大小写按照字母序排序。要实现这个算法不必对现有代码大加改动只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串实际上就是先把字符串都变成大写或者都变成小写再比较。
这样我们给sorted
传入key函数即可实现忽略大小写的排序
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
要进行反向排序不必改动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
当我们调用lazy_sum()
时返回的并不是求和结果而是求和函数
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f
时才真正计算求和的结果
>>> f()
25
当lazy_sum
返回函数sum
时相关参数和变量都保存在返回的函数中这种称为==“闭包Closure”==的程序结构拥有极大的威力。
闭包
注意到返回的函数在其定义内部引用了局部变量args
所以当一个函数返回了一个函数后其内部的局部变量还被新函数引用所以闭包用起来简单实现起来可不容易。
返回函数的实际执行时间
另一个需要注意的问题是返回的函数并没有立刻执行而是直到调用了f()
才执行
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中每次循环都创建了一个新的函数然后把创建的3个函数都返回了。
>>> f1()
9
>>> f2()
9
>>> f3()
9
原因就在于返回的函数引用了变量i
但它并非立刻执行。等到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)) # f(i)立刻被执行因此i的当前值被传入f() return fs
nonlocal
使用闭包就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值我们会发现返回的闭包函数调用一切正常但是如果对外层变量赋值由于Python解释器会把x
当作函数fn()
的局部变量它会报错
def inc():
x = 0
def fn():
# nonlocal x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
原因是==x
作为局部变量并没有初始化==直接计算x+1
是不行的。但我们其实是想引用inc()
函数内部的x
所以需要在fn()
函数内部加一个nonlocal x
的声明。加上这个声明后解释器把fn()
的x
看作外层函数的局部变量它已经被初始化了可以正确计算x+1
。
使用闭包时对外层变量赋值前需要先使用nonlocal声明该变量不是当前函数的局部变量。
匿名函数lambda
计算f(x)=x2
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
关键字lambda
表示匿名函数冒号前面的x
表示函数参数。
匿名函数有个限制就是只能有一个表达式不用写return
返回值就是该表达式的结果。
用匿名函数有个好处因为函数没有名字不必担心函数名冲突。此外匿名函数也是一个函数对象也可以把匿名函数赋值给一个变量再利用变量来调用该函数
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同样也可以把匿名函数作为返回值返回比如
def build(x, y):
return lambda: x * x + y * y
装饰器decorator
来源https://www.bilibili.com/video/BV1Vv411x7hj/?p=6&spm_id_from=pageDriver&vd_source=de55588165743d85bbfd39eae0d9c152
在代码运行期间动态增加功能的方式称之为“装饰器”Decorator。
现在给你一个函数在不修改函数源码的前提下实现在函数执行前和执行后分别输入"before"和"after"。
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
result = func()
print(result)
3.1 第一回合
利用闭包的思想实现
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return res
return inner
func = outer(func)
result = func()
print(result)
3.2 第二回合
利用python中支持特殊语法
'''
python中支持特殊语法在某个函数上方使用:
@函数名
def xxx():
pass
Python内部会自动执行函数名(xxx)执行完之后再讲结果赋值给xxx。
xxx=函数名(xxx)
'''
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return res
return inner
@outer # func = outer(func)
def func():
print("我是func函数")
value = (11, 22, 33, 44)
return value
# func = outer(func) # 等同与 xxx=函数名(xxx) 实现的功能
result = func()
print(result)
3.3 第三回合
请在这3个函数执行前和执行后分别输入"before"和"after"
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1()
func2()
func3()
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return res
return inner
@outer
def func1():
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer
def func2():
print("我是func2函数")
value = (11, 22, 33, 44)
return value
@outer
def func3():
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1()
func2()
func3()
优化支持n个参数
利用不定长参数 *args, **kwargs
def outer(origin):
def inner(*args, **kwargs):
print("before")
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func1(a1):
print("我是func1函数")
value = (11, 22, 33, 44)
return value
@outer
def func2(a1,a2):
print("我是func2函数")
value = (11, 22, 33, 44)
return value
@outer
def func3(a1):
print("我是func3函数")
value = (11, 22, 33, 44)
return value
func1(11)
func2(11,22)
func3(99)
小总结
●实现原理:基于@语法和函数闭包将原函数封装在闭包中然后将函数赋值为一个新的函数(内层函数)执行函数时再在内层函数中执行闭包中的原函数。
●实现效果:可以在不改变原函数内部代码和调用方式的前提下实现在函数执行和执行扩展功能。
●适用场景:多个函数系统统一在执行前后自定义一些功能。
●装饰器示例
def outer(origin):
def inner(*args, **kwargs):
# 执行前
res = origin(*args, **kwargs) #调用原来的func函数
# 执行后
return res
return inner
@outer
def func():
pass
func()
伪应用场景
在以后编写一个网站时如果项目共有100个页面其中99个是需要登录成功之后才有权限访问就可以基于装饰器来实现。
pip install flask
基于第三方模块Flask(框架)快速写一个网站:
基于装饰器实现的伪代码︰
from flask import Flask
app = Flask(__name__)
def auth(func):
def inner(*args,**kwargs):
# 判断用户是否已经登录已登录继续网下走未登录则返回登录页面。
res = func(*args,**kwargs)
return res
return inner
@auth
def index():
return "首页"
@auth
def info():
return "用户中心"
@auth
def order():
return "订单中心"
def login():
return "登录页面"
app.add_url_rule("/index/", view_func=index)
app.add_url_rule("/info/", view_func=info)
app.add_url_rule("/order/", view_func=order)
app.add_url_rule("/login/", view_func=login)
app.run()
重要补充:functools
你会发现装饰器实际上就是将原函数更改为其他的函数然后再此函数中再去调用原函数。
def admin():
'''这个是XXX的函数'''
print(112)
admin()
print(admin.__name__) # "admin"
print(admin.__doc__) # 这个是XXX的函数
加上了装饰器之后
def auth(func):
def inner(*args,**kwargs):
'''asfdsadfsf'''
res = func(*args,**kwargs)
return res
return inner
@auth
def admin():
'''这个是XXX的函数'''
print(112)
admin()
print(admin.__name__) # "inner"
print(admin.__doc__) # asfdsadfsf
即说明经过装饰器装饰之后这里的inner函数就待指admin函数
场景虽然我们知道经过装饰器装饰之后原函数被其他函数所替代了。但是我们还是想通过访问函数对象的属性时返回的还是他原来自己的值。
利用functools.wraps()
这个时候我们去执行admin()虽然它还是间接的执行的是inner()。但是这个时候通过访问函数对象的属性时返回的是他原来自己的值。
import functools
def auth(func):
@functools.wraps(func) # inner.__name__ = func.__name__ inner.__doc__ = func.__doc__
def inner(*args,**kwargs):
'''asfdsadfsf'''
res = func(*args,**kwargs)
return res
return inner
@auth
def admin():
'''这个是XXX的函数'''
print(112)
admin()
print(admin.__name__) # "admin"
print(admin.__doc__) # 这个是XXX的函数
其实一般情况下大家不用functoos也可以实现装饰器的基本功能但后期在项目开发时不加functols会出错内部会读取_name_且_name_重名的话就报错)所以在此大家就要规范起来自己的写法。
偏函数partial()
Python的functools
模块提供了很多有用的功能其中一个就是偏函数Partial function。要注意这里的偏函数和数学意义上的偏函数不一样。
当函数的参数个数太多需要简化时使用
functools.partial
可以创建一个新的函数这个新函数可以**固定住原函数的部分参数**从而在调用时更简单。【偏函数可以简化参数操作】
当函数的某个参数是我们可以提前获知的那我们就可以将它固定住
举个例子
# 定义一个取余函数默认和2取余
def mod(x,y=2):
# 返回 True 或 False
return x % y == 0
# 假设我们要计算和3取余如果不使用partial()函数那么我们每次调用mod()函数时都要写y=3
mod(4,y=3)
mod(6,y=3)
# 使用partial()函数
from functools import partial
mod_3 = partial(mod,y=3)
mod_3(4)
mod_3(6)
使用偏函数可以让我们减少填写参数的步骤也可以使代码更加简洁。
有的人说那我重新定义一个不也可以吗使用Ctrl C & Ctrl V无视函数体代码量多少再定义一个。
当然可以但是这样你的源码就会显得很臃肿试想一下有两个函数它俩的不同点就是默认参数值不一样源码看起来就繁琐读起来更是
总之使用偏函数可以使你的代码和操作更加简洁
int2 = functools.partial(参数1参数2参数3)
参数1函数对象
参数2*args 可变参数接收tuplelist
参数3*kw 关键字参数接收dict
例如
int2 = functools.partial(int'10'base=10)
参数1必填参数2和参数3可省略那就和原函数没区别了因为参数1就是原函数参数2就是可变参数参数3为关键字参数int自带关键字参数base当不传为默认值传入时必须以base=xxx的形式对应原始的关键字参数2传入的话会组装成tuple或list再通过*args 传入int*args