Pythin语言的函数(下)
前情回顾:
本篇文章是我对 Pythin语言的函数 理解的最后一讲,在开始进入本篇内容之前我们先回顾一下前面我都介绍了 Pythin语言的函数 的哪些内容:
-
什么是函数,它能做什么?
答:Python一切皆对象,函数也是一个 对象。它的功能是用来保存一些可执行的代码,并且可以在需要时,对这些语句进行 多次调用。 -
函数怎么创建?
答:创建函数的语法
def 函数名([形参1,形参2,形参3....]):
代码块
-
函数的参数有哪些形式?他们是怎么传递的?
答:函数的参数就两种:形参 和 实参;
形参(形式参数) 定义:形参就相当于在函数内部声明了 变量,但是 并不是赋值;
实参(实际参数) 指定对应 了函数在 创建时定义 的形参。在调用函数时必须传递 实参,实参将会赋值给对应的形参,简单来说:有几个形参就要有几个实参。
创建函数时需定义好函数需要哪些 形参;在调用函数时将传递进来的 实参 与创建函数时定义好的 形参 11对应,以便函数能正常的运转;如果,对传递进来的 实参 的数量无法确定,那么在创建函数时要将 形参 定义成 不定长参数(*) 。 -
什么是函数的 返回值?print() 不能做函数的返回值吗?
答:返回值就是函数执行以后返回的结果;通过 关键字return 来指定函数的返回值;我们可以通过一个变量来接收函数的返回值,或者可以直接函数来使用函数的返回值。
print() 方法用于打印输出,无法对函数运算后返回的值进行再操作;函数通过 关键字return 返回的值是可以进行再操作的。
return 后面可以跟 任意对象 ,返回值 甚至 可以是一个 函数。如果函数里仅仅只写一个 return 或者不写 return ,则相当于函数 return Noun。 -
什么是函数的作用域?
答:作用域(scope)指的是 变量生效的区域 ;函数在不同的位置它最终访问到的结果是不一样的。在Python中一共有两种作用域:全局作用域 和 函数作用域。对于在 在函数内部 修改 全局变量的操作,我们还学习了一个关键字 global 的功能和使用。 -
文档字符串 help() 是干嘛的?
答:help() 是 Python中内置函数,我们可以通过help()函数可以查询Python中 Python内置函数的用法;通过 help()函数 我们能看到 Python内置函数 隐藏 掉了的一些默认值。 -
什么是 递归函数 ?
答:函数的一种经典操作:自己调用自己。
在使用 递归函数 时必须要注意的两点:
1. 基线条件:问题可以被分解为 最小问题,当满足基线条件时,递归就 不执行 了;
2. 递归条件:可以将问题 继续分解 的条件。 -
在讲函数时,顺带着的介绍了一种代码的执行方式:命名空间。
答:所谓的 命名空间 只是代码执行的一种方式;代码在执行的时候是 以命名空间为单位进行执行 的代码。
回顾结束,回顾期间如果出现什么疑问或者不理解的,具体的详细细节回看前文案例。接下来开始我们函数的最后内容:高阶函数、函数的闭/解包 和 什么是 装饰器。
1. 高阶函数
在开始分享第一个内容之前,我希望大家带着这么一个问题来阅读本篇文章的内容:什么是 高阶函数 或者说是 高级函数?
先回答我为大家提出的问题: 高阶函数 或者说是 高级函数 它有 两个特点;如果满足,那么它就是 高阶函数 或者说是 高级函数。
特点一:接收 一个或多个函数 作为 参数;
特点二:将 函数 作为 返回值 返回。
接下来我们我们对上面的两个特点一一的进行详细的讲解。
参考实例:定义一个函数,可以将制定的列表中所有的偶数保存到一个新的列表中返回。
def fn(lst): # 定义一个函数:用来检测参数 lst 是否为偶数,并对其进行筛选
new_lst = []
for n in lst:
if n % 2 == 0:
new_lst.append(n)
return new_lst
a = [1,2,3,4,5,6,7,8,9,10]
print(fn(a)) # [2, 4, 6, 8, 10]
编程原则第一条:程序运行没有报错。
编程原则第二条:输出的结果是否符合需求。
大家在自己的编辑器中运行参考实例,得到的结果肯定是符合需求结果的。
那么,现在新的一个问题来了:上面的参考实例是一个 高阶函数 吗”?
很明显,它不是,它只是一个能进行正常计算的函数。那它为什么不是?因为 高阶函数 的两个特点它一个都没满足。既没有接收 一个或多个函数 作为 参数;也没有将 函数 作为 返回值 返回,参考实例返回的是一个列表。
OK,我们先放下它“是不是 高阶函数 ”这个问题,假如我们现在又有这么一个需求:定义一个函数,可以将制定的列表中所有的奇数保存到一个新的列表中返回。我们该怎么实现?
参考实例:
def fn(lst): # 定义一个函数:用来检测参数 lst 是否为奇数,并对其进行筛选
new_lst = []
for n in lst:
if n % 2 != 0:
new_lst.append(n)
return new_lst
a = [1,2,3,4,5,6,7,8,9,10]
print(fn(a)) # [1, 3, 5, 7, 9]
假如我们现在又有这么一个需求:定义一个函数,可以将制定的列表中所有的大于 5 的数保存到一个新的列表中返回。我们该怎么实现?
参考实例:
def fn(lst): # 定义一个函数:用来检测参数 lst 是否为大于 5 的数,并对其进行筛选
new_lst = []
for n in lst:
if n > 5 :
new_lst.append(n)
return new_lst
a = [1,2,3,4,5,6,7,8,9,10]
print(fn(a)) # [6, 7, 8, 9, 10]
假如我们现在又有这么一个需求:定义一个函数,可以将制定的列表中所有的大于 5 的奇数保存到一个新的列表中返回;
又有这么一个需求:定义一个函数,可以将制定的列表中所有的大于 8 的偶数保存到一个新的列表中返回;
…………(以上省去10万字需求)
怎么实现?
请大家放心,博主我不是纯粹的在水字数占用大家的阅读空间、浪费大家的阅读时间,而是带大家进行思考。毕竟 CSDN 上的上榜文章不是靠字数刷上去的。
但是以上省去10万字的各种需求,我们该怎么实现?(记住:我们只能提建议和思路,不要和甲方爸爸讲道理)或者说:我们希望我们创建的一个函数能实现多种功能?怎么办? 在这个函数里把所有的需求 都加上 是不是就可以了?必须可以!!!
此时是不是终于绕回到了今天的第一个主题: 高阶函数
OK,那么我们现在先来用 高阶函数 实现一个需求:定义一个函数,可以将制定的列表中所有大于5的偶数保存到一个新的列表中返回。
参考实例:
def fn(lst): # 定义一个高阶函数
def fn1(i): # 定义一个函数:用来检测一个数是否为偶数
if i % 2 == 0:
return True
def fn2(i): # 定义一个函数:用来检测一个数是否大于5
if i > 5:
return True
return False
nwe_lst = []
for n in lst:
if fn1(n):
if not fn2(n):
nwe_lst.append(n)
return nwe_lst
a = [1,2,3,4,5,6,7,8,9,10]
print(fn(a)) # [2, 4]
现在,甲方爸爸的所有需求,对于我们来说全部实现是没有问题了。但大家是不是又发现:如果后期需要维护或者修改上面的参考实例,实际操作起来会不会非常非常的麻烦,工作量会不会非常非常的大?那我们该怎么办?
现在之所以出现这样问题的原因是不是我把门最后的 if判断语句 给 写死了 呀。我们当然最希望我们创建的这个函数 跟根据不同的情况做出相应的不同的处理 啊;至于这个函数最后处理到什么情况,是不是应该由 传递相应参数的人 来定,而不是我们确定啊。那现在我们能不能做到:当使用我们函数的人给我们创建的函数传递什么信号,我们创建的函数就使用什么规则呢?当然也是 必须能!!!
参考实例:
def fn1(i): # 定义一个函数:用来检测一个数是否为偶数
if i % 2 == 0:
return True
def fn2(i): # 定义一个函数:用来检测一个数是否大于5
if i > 5:
return True
return False
def fn3(i): # 定义一个函数:用来检测一个数是否为3的倍数
if i % 3 == 0:
return True
return False
def fn(func,lst): # 定义一个高阶函数
nwe_lst = []
for n in lst:
if func(n):
nwe_lst.append(n)
return nwe_lst
a = [1,2,3,4,5,6,7,8,9,10]
print(fn(fn1,a)) # [2, 4, 6, 8, 10]
print(fn(fn2,a)) # [6, 7, 8, 9, 10]
print(fn(fn3,a)) # [3, 6, 9]
此时,我们是否发现:当我们传进不同函数的时候,高阶函数 的计算规则是不是就变了;我们再回想一下 高阶函数 的两个特点:接收 一个或多个函数 作为 参数;将 函数 作为 返回值 返回。
我为这个 高阶函数 写了这么多内容,那么我想问下大家:高阶函数 这个操作,有什么好处呢?
高阶函数 它的优势就在于:我们以前传递实参都是 整数(int)、字符串(str)、列表(list) ……这些都是 单一的数据;而当我们使用上了 高阶函数 之后,我们就不仅仅只是传递一些 单一的数据 了,还能传递3行、30行、300行……这些大块大块的代码块了。此时我们再来回看上面的这么多的内容,我写这么多,还是有点必要的 😃
当然了,对于 高阶函数 我们不能因为它的优势而对它进行 滥用,也要根据我们 实际的需求 和 程序的运转效率 合理的选择使用。
最后给大家留下一个小小的思考题:如果,我想要实现统计列表中 所有大于5的偶数 这个需求,该怎么操作?
2. 闭包
上面我们介绍了对函数的一种使用方式 高阶函数: 接收 一个或多个函数 作为 参数 进行传递;也可以将 函数 作为 返回值 进行返回。下面呢,我们来介绍函数的第二种使用方式 闭包。
什么是 闭包 呢?将 函数 作为 返回值 进行返回 的这种操作我们也称为 闭包。
咦,对 闭包 的介绍,大家是不是很眼熟。没错,确实是 高阶函数 的第二特点。即然前面已经介绍过 高阶函数 的第二特点了,再搞一个 闭包 它又有什么用呢?接下来和大家一起看一个实例:
def fn():
def fn1(): # 在函数内部定义一个函数
print('我是fn1')
return fn1 # 将函数作为 返回值 返回
print(fn()) # <function fn.<locals>.fn2 at 0x00000221EBA60940>
咦,为什么打印的是 函数fn 的 属性信息,而不是 我是fn1?在这里呢,我们再做一个小小的 前情回顾:print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
函数print() 它打印的是一个 对象信息;上面实例 函数print() 返回的是 内部函数fn2(),因为 返回值return 指向的是一个 函数对象fn1。
如果我们想要返回 我是fn1 该怎么办?我们需要通过自定义一个 变量,然后对 函数fn 进行调用,再然后打印调用到 函数fn 结果的 变量 就会得到
我是fn1 了。
参考实例:
def fn():
def fn2(): # 在函数内部定义一个函数
print('我是fn2')
return fn2 # 将函数作为 返回值 返回
print(fn()) # <function fn.<locals>.fn2 at 0x00000221EBA60940>
r = fn()
print(r()) # 我是fn2
# None <--不需要打印
r() # 我是fn2 <--直接调用就可以了
变量r 如何从一个没有值的变量变成了一个 函数?是因为 变量r 调用 高阶函数fn() 后 返回的函数,变量r 调用到的这个函数是在 高阶函数fn() 内定义的,并不是 全局函数。
对函数的作用域 我们在上一篇文章 9. Pythin语言的函数(中)里讲到过:
作用域指的是 变量生效的区域 ;全局变量可以在程序的任意位置进行访问,而局部变量它只能在函数内部被访问。再简单一点的总结就是:函数中的变量从里向外 看 是 可以 的,但是 无法 从外向里 看 的。
简单的回顾完函数的案例后,我们再回到参考实例:即然前面说 变量r 不是 全局函数,那么 函数执行r() 是不是就可以访问到 函数fn 中的变量。
参考实例:
def fn():
a = 10
def fn1(): # 在函数内部定义一个函数
print('我是fn2',a)
return fn1 # 将函数作为 返回值 返回
r = fn()
r() # 我是fn2 10
print(a) # NameError: name 'a' is not defined
上面 参考实例 这种对 高阶函数 调用的方式,我们统称之为 闭包。
此时,我们又要问了,不是已经有一个 高阶函数 了吗?为什么我们还要再多一个 闭包 这种怪怪的名词啊?是来搞大家的心态吗?其实不是的,接下来听我娓娓道来。
回到 参考实例,高阶函数 内的 变量a 是在 高阶函数fn 内部的,只能通过 r函数 才能获取到 变量a 的值,因为 r函数 不是 全局函数。那此时我们是不是可以这么理解:整个 高阶函数fn 是一个封闭的函数包,想要获取 高阶函数fn 内的信息,只能通过 r函数 来获取。
总结一下:
闭包的好处:
1)通过闭包可以创建一些只有 当前函数 能访问的 变量;
2)可以将一些 私有数据 藏到 闭包中(安全)。
接下来,我们通过一个 “求多个数的平均值” 这么一个案例来讲解一下 闭包的好处。
我们先抛开所有,先单纯看下,怎么实现需求。参考实例:
nums = [50,60,80,90,120]
# sum() 用来求一个列表中元素之和
print(sum(nums)/len(nums)) # 80.0
OK,通过上面这个参考实例我们已经知道 “5个数的平均值” 怎么求了;那如果出现 6个数、10个数、100个数、100000个数……的平均值该怎么实现呢?来,接下来我们继续完善。
nums = []
# 定义一个函数 计算平均值
def average(n):
# 将n添加到列表中
nums.append(n)
# 求平均值
return sum(nums) / len(nums)
print(average(10)) # 10.0
print(average(20)) # 15.0
print(average(30)) # 20.0
上面的参考实例虽然对 多个随机数 的输入方式还比较麻烦,但是对 “求多个数的平均值” 这个需求我们基本上还是实现了的。
现在我们来看一下上面的代码是否还有什么纰漏:在参考实例里nums = []
是一个全局变量,意味着我们可以在 任意位置 都可以访问到nums = []
;也意味着你能看得见、访问的到,别人同样也可以看得见、访问的到;你能修改nums = []
,别人也能修改nums = []
。
未来,在我们参加工作,开始和不同的人合作开发软件的时候,大家多少都会出现一些错误。比如说:
nums = []
# 定义一个函数 计算平均值
def average(n):
# 将n添加到列表中
nums.append(n)
# 求平均值
return sum(nums) / len(nums)
print(average(10))
nums.append('Python') # <---
print(average(20))
print(average(30))
#-----------------------------------------------------------------------------------
nums = []
nums = ['a','b',] # <---
# 定义一个函数 计算平均值
def average(n):
# 将n添加到列表中
nums.append(n)
# 求平均值
return sum(nums) / len(nums)
print(average(10))
print(average(20))
print(average(30))
各种不同的奇葩的问题,因为大量的代码而出现 一点也不足为奇。而因为这些细小的问题导致你的工作报废,更甚者导致了整个程序的奔溃,这样的问题是不是很可怕。此时,我们是否是希望nums = []
别谁都能随便更改,或者说:最起码别让它就这么四仰八叉的“裸露”在外面。
此时,就非常的需要函数的 闭包 了。而只要使用了函数的 闭包 就一定会使用到 函数的嵌套。
函数嵌套:
1)将内部函数作为返回值返回
2)内部函数必须要使用到外部函数的变量
参考实例:
def make_average():
nums = []
def average(n): # 定义一个函数 计算平均值
nums.append(n) # 将n添加到列表中
return sum(nums) / len(nums) # 求平均值
return average
nums = make_average()
print(nums(10)) # 10
print(nums(20)) # 15
print(nums(30)) # 20
print(nums(40)) # 25
最后,我们总结一下 形成闭包的条件:
1)函数嵌套;
2)将内部函数作为返回值返回;
3)内部函数必须要使用到外部函数的变量;你需要用到,但你又不希望别人使用的变量,确保变量的安全;不然你废起白咧的写这么多,还不太好领悟到的代码做什么。
3. 装饰器
装饰器 字面理解:一种用来进行 装饰 的工具。
这么解释这个名词还是很抽象了,那我们来举个例子,通过 “打比方,举例子” 来让大家能更好的理解这个名词。
现在社会,家家户户基本上都有属于自己的房子;现在各种什么房产中介啊,或者什么售楼中心啊都会推出什么 “精装修房” 或者什么 “简装修房”;不过呢,羊毛出在羊身上,在以前买卖房子都没这些修的,直接就是 “样板房”,一个大屋壳子,里面空荡荡的,什么都没有;此时,这样的房子是不是还不能住人的,买完房子后再怎么没钱,我们最少要 买一张床 吧;墙壁 灰扑扑的,最少要 用白石灰擦一擦 吧……以上行为等等等等,我们都统称这些行为叫做 装修。
其实我们 写代码 和 盖房子 的是一样一样的,不然我们也不会被称之为 码农 了,唯一不同的可能就是我们的工作场景还是有区别的吧。回归正题,即然新盖好的房子需要 装修,那我们刚写好还冒着热气的代码是不是也需要 装饰装饰。
例子 说完了,接下来开始介绍 Python语言的装饰器,我们该怎么使用。
3. 1 装饰器的引入
在开始介绍 装饰器的引入 之前,我们先定义一个 有意义 的函数,什么叫 有意义 的函数?或者说 我们之前写的哪些函数难道都没有什么意义吗?
我们这里说的 有意义 不是说 我们前面写的代码或者说案例能不能用;而是说 我们给自己写的代码块进行命名,在未来我们可能会反复的需要之前写过的那些内容的时候,我们可以灵活的反复调用 这样的 代码块 我们称之为 有意义 的函数。
我们现在来定义两个 有意义 的函数。参考实例:
def nums_add(*n): # 求任意数的 和
i = 0
for a in n:
i += a
return i
def nums_mul(*n): # 求任意数的 积
i = 1
for a in n:
i *= a
return i
# 打印测试一下是否有误:
r = nums_add(1, 2, 3)
c = nums_mul(2, 3, 4)
print(r) # 6
print(c) # 24
接下来呢我们对我们刚定义的两个 有意义 的函数添加一些需求,什么需求呢:我希望函数在计算前出现一句提示,叫“计算开始”;等函数计算结束后出现一句提示,叫“计算结束”。我们该怎么进行操作?参考实例:
def nums_add(*n): # 求任意数的 和
print('计算开始...')
print('计算结束:')
i = 0
for a in n:
i += a
return i
def nums_mul(*n): # 求任意数的 积
print('计算开始...')
print('计算结束:')
i = 1
for a in n:
i *= a
return i
# 打印测试一下是否有误:
r = nums_add(1, 2, 3)
print(r) # 计算开始...
# 计算结束:
# 6
c = nums_mul(2, 3, 4)
print(c) # 计算开始...
# 计算结束:
# 24
需求效果现在是已经实现了,但是我们写的代码是否有点怪怪的。代码 print('计算开始...') 和 print('计算结束:')
在源码里是否重复的出现了两次了。我们可以直接通过修改函数中的代码来完成一些需求,但是会产生一些问题。会出现哪些问题:
问题1) 我们现在只是两个函数还好,如果出现了 n个函数 都有一样的需求,该怎么办?换个表达方式 如果要 修改的函数比较多,修改起来 非常麻烦;
问题2) 不方便 后期维护;
问题3) 这样会违反 程序设计的开闭原则(OCP);程序的设计要求 开放对程序的扩展,要关闭对程序的修改。
现在回到上面的实例,上面实例是否完全的违反了 程序的设计开闭原则(OCP)。那我们现在能否在尽可能的 不修改 源程序(源代码)的基础上,对 新增的需求 进行 扩展。参考实例:
def nums_add(*n): # 求任意数的 和
i = 0
for a in n:
i += a
return i
def nums_mul(*n): # 求任意数的 积
i = 1
for a in n:
i *= a
return i
def fn(funs): # 甲方爸爸后续添加的需求
print('函数开始运算...')
print(funs)
return '函数运算结束。'
# 打印测试一下是否有误:
r = fn(nums_add(1, 2, 3))
print(r) # 函数开始运算...
# 6
# 函数运算结束。
c = fn(nums_mul(2, 3, 4))
print(c) # 函数开始运算...
# 24
# 函数运算结束。
此时,函数fn()
是不是就是 函数nums_add()
和 函数nums_mul()
的 装饰函数。在不修改 函数nums_add()
和 函数nums_mul()
源代码 的基础上对 最后输出的结果 进行了相应的 调整 或 修改。
当然,现在我们只是为了实现 实例效果 只是设置了一些简单的 装饰函数;但是在未来,我们在参加开发各种大型项目的时候会不会需要很多很多的 功能 或者 效果 需要 引用,此时就是 Python语言的装饰器 展现它强大力量的时候了。
3.2 装饰器的使用
上面我们简单的介绍了 程序的编写需要符合 程序设计的开闭原则(OCP);什么是 装饰器(装饰函数);为什么需要 装饰器(装饰函数) 的引入。
接下来我们详细的来讲解一下 装饰器的使用。
老习惯,先来一个参考实例:
def start_end(): # 用来对其他的函数进行扩展,扩展函数执行的时候 打印 “开始执行” 和 “执行结束”
def new_function(): # 创建一个函数
pass # 占位
return new_function # 返回一个函数
# 打印测试一下是否有误:
f1 = start_end()
f2 = start_end()
print(f1) # <function start_end.<locals>.new_function at 0x000002008C240940>
print(f2) # <function start_end.<locals>.new_function at 0x000002008C240A60>
虽然,变量f1
和 变量f2
调用的都是 函数start_end()
,但实际上我们调用的是 函数new_function()
运算后的结果 对不对。现在呢,这里有个问题:变量f1
和 变量f2
两次调用的 函数start_end()
是同一个函数吗?答案呢 肯定不是。虽然,变量f1
和 变量f2
两次调用的 函数都叫 start_end()
;但是,最后通过打印这两个变量获得的函数所在的 内存地址 却是 完全不一样。
当然了,上面的问题呢都是一些小插曲;上面的 参考实例 到目前为止 我们发现我们设置的 变量 调用了 函数start_end()
最后给我们返回了 函数new_function()
就这一个结果,并没有什么其他的功能。和我们说的 用函数start_end()
来对其他的函数进行扩展,扩展函数执行的时候 打印 “开始执行” 和 “执行结束” 这个需求还有十万八千里,最起码现在还是什么功能都没有。那现在我们来对返回的 函数new_function()
里加点东西吧。参考实例:
def fn():
print('我是fn函数')
def start_end(): # 用来对其他的函数进行扩展,扩展函数执行的时候 打印 “开始执行” 和 “执行结束”
def new_function(): # 创建一个函数
print('函数开始运算...')
fn() # <-----
print('函数运算结束。')
return new_function # 返回一个函数
f1 = start_end()
f1() # 函数开始运算...
# 我是fn函数
# 函数运算结束。
通过上面的 参考实例 以及 最后的输出结果 发现 函数start_end()
已经对 函数fn()
的输出结果进行了扩展,而且函数运行最后的结果非常的OK啊。那按照这个格式 函数start_end()
也可以对 函数nums_add()
的输出结果进行了扩展。参考实例:
def nums_add(*n): # 求任意数的 和
i = 0
for a in n:
i += a
return i
def start_end(): # 用来对其他的函数进行扩展,扩展函数执行的时候 打印 “开始执行” 和 “执行结束”
def new_function(): # 创建一个函数
print('函数开始运算...')
nums_add() # <-----
print('函数运算结束。')
return new_function # 返回一个函数
f1 = start_end()
f1()
我们先别在意 参考实例 最后输出的结果,因为大家如果仔细看就会发现上面的 参考实例 最后输出的结果一定是会报错的;但我之所以还要引用这个带有错误 参考实例 就是想表达:虽然 函数start_end()
可以对 函数fn()
和 函数nums_add()
的输出结果进行了扩展;但是修改他们的基础还是在修改 函数start_end()
里的源代码;对于 函数start_end()
的操作是不符合 程序设计的开闭原则(OCP) 的。那为什么会出现这样的结果呢?是因为我们把 函数start_end()
里 负责扩展的函数 那一块( fn()、nums_add() )写死了;是不是 因为我们不知道要对哪块代码进行扩展,所以我们对 负责扩展的函数 那一块 不能写死 呀;所以,我们需要对 想要扩展的函数 以 参数 的形式 传递进来。现在既然找到问题所在,那让我们一起来修补这块的漏洞。
说到这,我们来穿插一个小的新知识点:形参 old 、形参 *args 、形参 **kwargs
形参 old:接收要扩展的函数对象。
形参 *args :接收所有 位置参数 的不定长参数
形参 **kwargs :接收所有的 关键字参数
参考实例:
def fn():
print('我是fn函数')
def nums_add(*n): # 求任意数的 和
i = 0
for a in n:
i += a
return i
def start_end(old): # 用来对其他的函数进行扩展,扩展函数执行的时候 打印 “开始执行” 和 “执行结束”
def new_function(*args,**kwargs): # 创建一个函数,接收需要进行扩展的函数 以及 需要进行扩展的函数的形参
# 装包,把 参数 包装成一个一个的元组,或者一个一个的字典
print('函数开始运算...')
# 把位置参数拆包成一个元组 或者 把关键字参数拆包成一个字典 传递进去
result = old(*args,**kwargs) # 接收需要进行扩展的函数 以及 需要进行扩展的函数的形参
print('函数运算结束。')
return result
return new_function
f1 = start_end(fn) # 以后如果再想扩展哪个函数,直接调用 函数start_end() 形成一个新的函数,就是对原有函数的一个新的 扩展
c = f1 #
c() # 函数开始运算...
# 我是fn函数
# 函数运算结束。
f2 = start_end(nums_add) # 以后如果再想扩展哪个函数,直接调用 函数start_end() 形成一个新的函数,就是对原有函数的一个新的 扩展
r = f2(1,2,3,4)
print(r) # 函数开始运算...
# 函数运算结束。
# 10
像我们今天用的 函数start_end(old)
类似于这样的函数,我们统称为 装饰器。通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展;在开发中,我们都是通过装饰器来扩展函数的功能的。
当然,讲 装饰器 绝对不能少不了 @ ,不然就是对 装饰器 的亵渎。
参考实例:
def start_end(old): # 参数 old 接收要扩展的函数对象 以及 需要进行扩展的函数的形参
def new_function(*n):
print('函数开始运算...')
result = old(*n) # 参数 old 接收要扩展的函数对象 以及 需要进行扩展的函数的形参
print('函数运算结束。')
return result
return new_function
@start_end
def speak():
print('大家加油')
speak()
# 函数开始运算...
# 大家加油
# 函数运算结束。
回到最初我们介绍 装饰器 时提到的 装饰器 解决为我们在开发过程中解决的几个问题:
问题1) 如果要 修改的函数比较多,修改起来 非常麻烦;
问题2) 不方便 后期维护;
问题3) 这样会违反 程序设计的开闭原则(OCP);程序的设计要求 开放对程序的扩展,要关闭对程序的修改。
总结小便条
-
高阶函数
• 接收函数作为参数,或者将函数作为返回值返回的函数就是高阶函数 -
闭包
• 将函数作为返回值也是高阶函数我们也称为闭包
• 闭包的好处:
通过闭包可以创建一些只有当前函数能访问的变量;
可以将一些私有数据藏到闭包中。
• 行成闭包的条件:
函数嵌套;
将内部函数作为返回值返回;
内部函数必须要使用到外部函数的变量。
-
装饰器的引入
• 我们可以直接通过修改函数中的代码来完成需求,但是会产生以下一些问题
• 如果修改的函数多,修改起来会比较麻烦
• 不方便后期的维护
• 这样做会违反开闭原则(ocp)
• 程序的设计,要求开发对程序的扩展,要关闭对程序的修改 -
装饰器的使用
• 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
• 在开发中,我们都是通过装饰器来扩展函数的功能的