目录
简介
高阶函数
map
filter
匿名函数
返回函数
闭包
装饰器
双装饰器
装饰器模板
思考题
参考
函数式编程是种编程范式(函数式编程、程序编程、面向对象编程、指令式编程等)。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
函数式编程关心数据的映射,命令式编程关心解决问题的步骤
这里的映射就是数学上「函数」的概念——一种东西和另一种东西之间的对应关系。
这也是为什么「函数式编程」叫做「函数」式编程。(知乎——什么是函数式编程思维)
高阶函数是至少满足下列一个条件的函数:
接受一个或多个函数作为输入
输出一个函数
在看下面的内容之前,你需要转变一下思想:
函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
为了让读者更清晰地理解,在上一篇文章Python-函数基础总结与内置函数中,将print函数赋值给了变量display,类似于函数指针或者说给print函数起了个别名。
>>> display
map
(function, iterable, ...)
返回一个将 function 应用于 iterable 中每一项并输出其结果的迭代器。 如果传入了额外的 iterable 参数,function 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。 当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。
仍以上一篇的函数为例:
def power(x, n=2):
return x ** n
如果需要一个1~10的平方组成的列表,你会怎么做呢?for循环,调用power,添加到列表中?使用map一行就搞定了。如果是立方呢?请看下面:
t = (range(1, 11))
n = (3, 3, 3, 3, 3, 3, 3, 3, 3, 3)
res = map(power, t)
print(type(res))
print(list(res))
res = map(power, t, n)
print(list(res))
结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
python3没有reduce?不,它被放到了functools模块中,需要导入。这是为什么呢?博主也很好奇,于是去找了一下,原来不止是reduce,python语言的设计者吉多·范罗苏姆(Guido van Rossum)甚至想将lambda、reduce、filter、map函数都删除。
最后他只从标准库中删除了reduce,并放到了functools里面。他不想自己设计的语言引入其他语言中抽象复杂的部分,python更多的是作为一种面向对象的脚本语言,而不是函数式编程语言。
参考:The fate of reduce() in Python 3000
filter
(function, iterable)
用 iterable 中 传入函数 function 会返回真的那些元素,构建一个新的迭代器。iterable 可以是一个序列,一个支持迭代的容器,或一个迭代器。如果 function 是 None
,则会假设它是一个身份函数,即 iterable 中所有返回假的元素会被移除。
一句话:利用函数对序列进行过滤/筛选,如同名字一样。
得到一个列表包含100以内的所有素数:
def prime(num):
tmp = math.sqrt(num)
i = 2
while i <= tmp:
if num % i == 0:
return False
i += 1
return True
t = (range(2, 100))
res = filter(prime, t)
print(type(res))
print(list(res))
结果:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
大多数情况下,列表生成式可以完成map和filter的功能。
map 相当于[F(x) for x in S],filter相当于for x in S if P(x)]
t = (range(1, 11))
l = [power(x) for x in t]
l = [power(x,3) for x in t]
t = (range(2, 100))
l = [x for x in t if prime(x)]
有关列表更多内容,可查看:Python-列表总结(操作符、方法、内置函数、相关模块)
一些时候只会在一个地方调用一个很简单的函数,没必要将他们使用def显示的定义出来,这个时候就用到了匿名函数。
Lambda函数可以在需要函数对象的任何地方使用。它们在语法上限于单个表达式。语法如下:
lambda [arg1,arg2,....]:expression
中括号代表可选,也就是说Lambda函数可以有>=0个参数
对于上一篇文章的plus函数:
def plus(a, b):
return a + b
可以写成:
lambda a,b:a+b
对于power函数,可以写成:
lambda x:x**2
同样,lambda可以作为参数传给其他函数,例如,传给map:
res = map(lambda x: x ** 2, t)
说完了函数作为参数,我们就来说说函数作为返回值,也就是返回一个函数。
既然返回一个函数,我们就需要在一个函数中再定义一个函数,有种套娃的感觉。
def plus_not_now(*t):
def plus_all():
return sum(t)
return plus_all
tp = (range(1, 6))
fun = plus_not_now(*tp)
print(type(fun))
print(fun())
我们在plus_not_now中又定义了一个函数plus_all,并将它作为结果返回(注意,不是返回plus_all())。
结果:
15
调用函数fun时才真正运行plus_all的内容。
这里有个奇怪的事情,我们在上面的程序后面再定义一个变量fun2,打印一下两个id
fun2 = plus_not_now(*tp)
print(id(fun))
print(id(fun2))
结果:
15
2005949437416
2005950885320
怎么会不一样呢?这个问题就作为本篇文章的思考题吧!
当一个内嵌函数引用其外部作用域的变量,我们就会得到一个闭包。创建一个闭包必须满足以下几点
:
前面的返回函数就是闭包
注意,尽量不要引用变化变量
def plus():
funcs = []
for i in range(3):
def add():
return i + i
funcs.append(add)
return funcs
if __name__ == '__main__':
p1, p2, p3 = plus()
print(p1(), p2(), p3())
结果为
4 4 4
等你运行p1时,i已经变为了2,不是0,当然,你可以将参数绑定:
def plus():
funcs = []
def bind(j):
def add():
return j + j
return add
for i in range(3):
funcs.append(bind(i))
return funcs
装饰器:在代码运行前后动态增扩展功能的方式
目前有一些业务已经上线了,现在需要增加验证功能
def add(a, b):
print(a + b)
def multi(a, b):
print(a * b)
def devision(a, b):
print(a / b)
你可能这样写,但如果不让你修改已上线业务代码呢?
def calling(name):
print(f'{name}正在被调用...')
def add(a, b):
calling('add')
print(a + b)
def multi(a, b):
calling('multi')
print(a * b)
def devision(a, b):
calling('devision')
print(a / b)
你可以这样
def addcalling(func):
def calling(a, b):
print(f'{getattr(func, "__name__")}正在被调用...')
func(a, b)
return calling
def add(a, b):
print(a + b)
def multi(a, b):
print(a * b)
def devision(a, b):
print(a / b)
if __name__ == '__main__':
add = addcalling(add)
add(3, 5)
函数指向了内部函数,multi这些也可以,当然,Python中使用 @装饰器 即可自动让装饰器将下面的函数传入并返回装饰后的函数
def addcalling(func):
def calling(a, b):
print(f'{getattr(func, "__name__")}正在被调用...')
func(a, b)
return calling
@addcalling
def add(a, b):
print(a + b)
@addcalling
def multi(a, b):
print(a * b)
@addcalling
def devision(a, b):
print(a / b)
if __name__ == '__main__':
add(3, 5)
multi(3, 5)
devision(3, 5)
@addcalling 等价于 func = addcalling(func),所以,即使不运行add(3,5),在解释器解释python代码的过程中碰到@addcalling也会运行addcalling函数。
在上面的基础上,再添加一个计算函数运行时间的装饰器
import time
def addcalling(func):
def calling(a, b):
print(f'{getattr(func, "__name__")}正在被调用...')
func(a, b)
return calling
def addtime(func):
def runtime(a, b):
starttime = time.time()
func(a, b)
endtime = time.time()
print(f'运行时间{endtime - starttime}')
return runtime
@addtime
@addcalling
def add(a, b):
print(a + b)
@addtime
@addcalling
def multi(a, b):
print(a * b)
@addtime
@addcalling
def devision(a, b):
print(a / b)
if __name__ == '__main__':
add(3, 5)
multi(3, 5)
devision(3, 5)
addtime虽然写在前面,但其实是先用addcalling装饰后再用的addtime,类似栈,可以在addtime的时候输出一下函数名
def addtime(func):
def runtime(a, b):
starttime = time.time()
func(a, b)
print(getattr(func, "__name__"))
endtime = time.time()
print(f'运行时间{endtime - starttime}')
结果
add正在被调用...
8
calling
运行时间0.0
def wrapper_args(*args, **kwargs):
def wrapper(func):
# 装饰前需要运行的语句
def inner(*args, **kwargs):
# 被装饰函数运行前需要执行的语句
ret = func(*args, **kwargs)
# 被装饰函数运行后,得到返回值前需要执行的语句
return ret
# 装饰后,返回函数前需要运行的语句
return inner
return wrapper
例如,在Flask等框架中有@app.route(“/”),来添加路由,这就是含参装饰器,上面应该是装饰器的很全的版本了。
或者使用类实现装饰器
class Wrapper(object):
def __init__(self, func):
self.__func = func
def __call__(self, *args, **kwargs):
# 被装饰函数运行前需要执行的语句
ret = self.__func(*args, **kwargs)
# 被装饰函数运行后,得到返回值前需要执行的语句
return ret
装饰器的应用:
1.上一篇文章我把print函数赋值给了一个变量display
>>> display=print
>>> id(print)
1777190314296
>>> id(display)
1777190314296
它们的id是一样的,而在上面的返回函数中,fun与fun2的id是不一样的,请思考下为什么不一样?
Python官方文档-函数式编程指引
更多python相关内容:【python总结】python学习框架梳理
本人b站账号:lady_killer9
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。如果您感觉有所收获,自愿打赏,可选择支付宝18833895206(小于),您的支持是我不断更新的动力。