python的函数部分——高阶函数,闭包和装饰器


高阶函数

  • 高阶函数满足:接收函数作为参数,或者将函数作为返回值的函数是高阶函数

  • 当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数

  • 例如一个列表中的元素,想要取出其中一些满足条件的元素

    # 创建一个列表
    l = [1,2,3,4,5,6,7,8,9,10]
    
    # 定义一个函数
    #   可以将指定列表中的所有的偶数,保存到一个新的列表中返回
    
    # 定义一个函数,用来检查一个任意的数字是否是偶数
    def fn2(i) :
        if i % 2 == 0 :
            return True
    
        return False    
    
    # 这个函数用来检查指定的数字是否大于5
    def fn3(i):
        if i > 5 :
            return True    
        return False
    
    def fn(func , lst) :
    
        '''
            fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回
    
            参数:
                lst:要进行筛选的列表
        '''
        # 创建一个新列表
        new_list = []
    
        # 对列表进行筛选
        for n in lst :
            # 判断n的奇偶
            if func(n) :
                new_list.append(n)
            # if n > 5 :
            #     new_list.append(n)
    
        # 返回新列表
        return new_list
  • 对于上图所示代码,只需要调用fn函数并且传入选择的函数和要被选择的列表即可。

闭包

  • 刚刚说到高阶函数可以是将函数作为参数传入,还有一种返回函数的情况,闭包就是利用了这个。

  • 假设我们需要一个求平均数的函数,我可能会这么写

    # 首先创建一个列表
    nums = []
    
    def average(n):
        '''
            将n插入到数列中,并且计算平均数。
        '''
        nums.append(n)
        return sum(nums) / len(nums)
    
    print(average(10))
    # 输出10
    print(average(10))
    # 输出10
    print(average(20))
    # 输出13.333333333
  • 但是设想如果有一个人在代码里加入了一个重名的nums变量,就会覆盖我们设置的nums变量,例如

    # 首先创建一个列表
    nums = []
    
    def average(n):
        '''
            将n插入到数列中,并且计算平均数。
        '''
        nums.append(n)
        return sum(nums) / len(nums)
    
    print(average(10))
    # 输出10
    print(average(10))
    # 输出10
    nums = []
    print(average(20))
    # 输出20
  • 这样我们的程序就会被破坏,这时就轮到闭包出场

    def make_averager():
        # 创建一个列表,用来保存数值
        nums = []
    
        # 创建一个函数,用来计算平均值
        def averager(n) :
            # 将n添加到列表中
            nums.append(n)
            # 求平均值
            return sum(nums)/len(nums)
    
        return averager
    
    averager = make_averager()
    
    print(averager(10))
    print(averager(20))
    print(averager(30))
    print(averager(40))
  • 这样的话,nums列表对外来说就是不可见的,在全局中修改/创建nums变量也不会有任何问题。

装饰器

  • 我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题

    ① 如果要修改的函数过多,修改起来会比较麻烦

    ② 并且不方便后期的维护

    ③ 并且这样做会违反开闭原则(OCP)

    程序的设计,要求开发对程序的扩展,要关闭对程序的修改

  • 如果在不修改原函数的情况下,对原函数进行扩展,可以用下列方法:

    def fn():
        print('我是fn函数....')
    
    # 只需要根据现有的函数,来创建一个新的函数
    def fn2():
        print('函数开始执行~~~')
        fn()
        print('函数执行结束~~~')
  • 上边的方式,已经可以在不修改源代码的情况下对函数进行扩展了

  • 但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦了

  • 为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们生产函数,于是产生了下列代码

    def begin_end(old):
        '''
            用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    
            参数:
                old 要扩展的函数对象
        '''
        # 创建一个新函数
        def new_function(*args , **kwargs):
            print('开始执行~~~~')
            # 调用被扩展的函数
            result = old(*args , **kwargs)
            print('执行结束~~~~')
            # 返回函数的执行结果
            return result
    
        # 返回新函数        
        return new_function
    
    f = begin_end(fn)
    f2 = begin_end(add)
    f3 = begin_end(mul)
    
    # 向begin_end()这种函数我们就称它为装饰器
    #   通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
    #   在开发中,我们都是通过装饰器来扩展函数的功能的
    # 在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数
    #   可以同时为一个函数指定多个装饰器,这样函数将会安装从内向外的顺序被装饰 
  • 例如“begin_end()”这种函数我们就称它为装饰器

  • 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展

  • 在开发中,我们都是通过装饰器来扩展函数的功能的

  • 在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数

  • 可以同时为一个函数指定多个装饰器,这样函数将会安装从内向外的顺序被装饰

    def fn3(old):
        '''
            用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    
            参数:
                old 要扩展的函数对象
        '''
        # 创建一个新函数
        def new_function(*args , **kwargs):
            print('fn3装饰~开始执行~~~~')
            # 调用被扩展的函数
            result = old(*args , **kwargs)
            print('fn3装饰~执行结束~~~~')
            # 返回函数的执行结果
            return result
    
        # 返回新函数        
        return new_function
    
    @fn3
    @begin_end
    def say_hello():
        print('大家好~~~')
    
    say_hello()

参考:阿里云大学

Last modification:April 6th, 2020 at 10:58 pm