10. Pythin语言的函数(下)

前情回顾:

  本篇文章是我对 Pythin语言的函数 理解的最后一讲,在开始进入本篇内容之前我们先回顾一下前面我都介绍了 Pythin语言的函数 的哪些内容:

  1. 什么是函数,它能做什么?
    答:Python一切皆对象函数也是一个 对象。它的功能是用来保存一些可执行的代码,并且可以在需要时,对这些语句进行 多次调用

  2. 函数怎么创建?
    答:创建函数的语法

def 函数名([形参1,形参2,形参3....]):
    代码块
  1. 函数的参数有哪些形式?他们是怎么传递的?
    答:函数的参数就两种:形参实参
      形参(形式参数) 定义:形参就相当于在函数内部声明了 变量,但是 并不是赋值
      实参(实际参数) 指定对应 了函数在 创建时定义形参。在调用函数时必须传递 实参实参将会赋值给对应的形参,简单来说:有几个形参就要有几个实参
      创建函数时需定义好函数需要哪些 形参;在调用函数时将传递进来的 实参 与创建函数时定义好的 形参 11对应,以便函数能正常的运转;如果,对传递进来的 实参 的数量无法确定,那么在创建函数时要将 形参 定义成 不定长参数(*)

  2. 什么是函数的 返回值?print() 不能做函数的返回值吗?
    答:返回值就是函数执行以后返回的结果;通过 关键字return 来指定函数的返回值;我们可以通过一个变量来接收函数的返回值,或者可以直接函数来使用函数的返回值。
    print() 方法用于打印输出,无法对函数运算后返回的值进行再操作;函数通过 关键字return 返回的值是可以进行再操作的。
    return 后面可以跟 任意对象 ,返回值 甚至 可以是一个 函数。如果函数里仅仅只写一个 return 或者不写 return ,则相当于函数 return Noun。

  3. 什么是函数的作用域?
    答:作用域(scope)指的是 变量生效的区域 ;函数在不同的位置它最终访问到的结果是不一样的。在Python中一共有两种作用域:全局作用域函数作用域。对于在 在函数内部 修改 全局变量的操作,我们还学习了一个关键字 global 的功能和使用。

  4. 文档字符串 help() 是干嘛的?
    答:help() 是 Python中内置函数,我们可以通过help()函数可以查询Python中 Python内置函数的用法;通过 help()函数 我们能看到 Python内置函数 隐藏 掉了的一些默认值。

  5. 什么是 递归函数
    答:函数的一种经典操作:自己调用自己。
    在使用 递归函数 时必须要注意的两点:
      1. 基线条件:问题可以被分解为 最小问题,当满足基线条件时,递归就 不执行 了;
      2. 递归条件:可以将问题 继续分解 的条件。

  6. 在讲函数时,顺带着的介绍了一种代码的执行方式:命名空间。
    答:所谓的 命名空间 只是代码执行的一种方式;代码在执行的时候是 以命名空间为单位进行执行 的代码。

  回顾结束,回顾期间如果出现什么疑问或者不理解的,具体的详细细节回看前文案例。接下来开始我们函数的最后内容:高阶函数函数的闭/解包 和 什么是 装饰器

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);程序的设计要求 开放对程序的扩展,要关闭对程序的修改

  
  
  

总结小便条

  1. 高阶函数
    • 接收函数作为参数,或者将函数作为返回值返回的函数就是高阶函数

  2. 闭包
    • 将函数作为返回值也是高阶函数我们也称为闭包

   • 闭包的好处:
    通过闭包可以创建一些只有当前函数能访问的变量;
    可以将一些私有数据藏到闭包中。

   • 行成闭包的条件:
    函数嵌套;
    将内部函数作为返回值返回;
    内部函数必须要使用到外部函数的变量。

  1. 装饰器的引入
    • 我们可以直接通过修改函数中的代码来完成需求,但是会产生以下一些问题
    • 如果修改的函数多,修改起来会比较麻烦
    • 不方便后期的维护
    • 这样做会违反开闭原则(ocp)
    • 程序的设计,要求开发对程序的扩展,要关闭对程序的修改

  2. 装饰器的使用
    • 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
    • 在开发中,我们都是通过装饰器来扩展函数的功能的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章