python —— 高阶函数、嵌套函数与装饰器


前言

  本篇文章,贯穿始终的示例是:为了不改变原函数的基础上,为其加上开始和终止的时间戳。

import time

def func():
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

func()

  如果在上面例子中加入注释内的语句就等于我们修改了原函数,所以不符合我们的要求。所以我们用到了装饰器。
  提前写一下知识体系,方便我们理解和记忆:
在这里插入图片描述

一、什么是装饰器

1.1 装饰器的概念及特点

  让其他函数或类在不需要做任何代码修改的前提下,在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。打个比方,你买了一个生日礼物,想对齐包装装饰一下送出去。那么我们肯定是在外面套一个好看的包装纸、装饰个花或者写点字什么的。我们不会动礼品本身。
  装饰器有两个特点:

  1. 装饰器不能修改被装饰函数的源码
  2. 装饰器不能修改被装饰函数的调用方式

二、高阶函数

   构造函数的第一步是构造一个高阶函数。

2.1 什么是高阶函数

   符合下列条件之一的函数就是高阶函数:

  1. 接受函数名作为形参
  2. 返回值中包含函数名

   也就是说,一般函数是处理变量,返回变量值的。而高阶函数是处理函数,返回函数的。

2.2 变量与函数

  上一节介绍什么是高阶函数时我们多次提到了函数名,那我们先探讨一下什么是函数名,什么是变量,他们有什么关系。
  我们所说的python中的变量名与函数名,其实就是我们电脑中的一串内存地址,我们调用变量和函数的时候都是对其对应内存地址内的数据的调用。

>>> a = 5
>>> b = a  # 使得b指向a所存储数据的内存地址,也就是把5这个数据所在的内存地址赋值给b
>>> # 输出a, b的值
>>> print('a的值为:', a)
a的值为: 5
>>> print('b的值为:', b)
b的值为: 5
>>> # 输出a, b的内存地址,应该是相同的
>>> print('a的内存地址为:', id(a))
a的内存地址为: 3068430936496
>>> print('b的内存地址为:', id(b))
b的内存地址为: 3068430936496
>>>

函数也是一样的,可以参考Python —— 函数或类后面括号的作用

def func(arg):
    print(f'函数名为{arg}')


b = func  # 使得b指向func函数所存在的内存地址,也就是把函数func所在的内存地址赋值给b
# 执行func, b函数
func('func')
b('b')
# 输出func, b的内存地址,应该是相同的
print('a的内存地址为:', id(func))
print('b的内存地址为:', id(b))

"""
输出如下:
函数名为func
函数名为b
a的内存地址为: 2103323058240
b的内存地址为: 2103323058240
"""

2.3 高阶函数的使用及意义

  我们先以前言中提到的目的为例:

import time


def func():
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


def higher_order_func(input_func):  # 这里给高阶函数设置了形参,形参为一个函数名
        print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
        return input_func  # 这里返回了一个函数名
        print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

# 1. 这里我们将函数名func作为形参传入
# 2. 执行的higher_order_func函数并在中途返回了input_func
# 3. 相当于我们又重新拿到了作为实参传入的func
# 4. 我们再去执行他func()
func = higher_order_func(func)

func()

输出如下:

10:33:09 开始运行程序
程序主体正在运行中……

  这里面的逻辑相对是比较绕的,我们用下图做说明:
在这里插入图片描述
  在这里我们可以看到高阶函数的使用意义:

  1. 接受函数名作为形参(这样可以不改变被装饰代码的前提下增加功能)
  2. 返回值中包含函数名(不改变被装饰函数的调用方式)

  但是细心的人就会从上面代码的执行结果发现两个问题:

  1. 开始运行程序的打印信息不是在执行func()函数的时候打印出来的,而是在func = higher_order_func(func)时就已经打印了。如果两行代码中间有其他代码执行,那么这个时间就是不准的。
  2. 上述代码中程序运行结束没有打印出来

  这里我们就需要用到嵌套函数来解决这个问题。

三、嵌套函数

2.1 什么是嵌套函数

  嵌套函数,顾名思义,就是函数里面套了一个函数,形式如下:

def outer_func():
    def inner_func():
        pass
    inner_func()
    return inner_func

  上面的例子中就是outer_func()里面又定义了inner_func()。我们可以在outer_func()内部调用它使用或者return后在外部调用他使用。这是后话我们后面再说

2.2 嵌套函数的意义

  现在我们来解决我们上一章节留下来的问题。代码示例如下:

import time


def func():
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


def higher_order_func(input_func):  # 这里给高阶函数设置了形参,形参为一个函数名
    def add_info():
        print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
        input_func()
        print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

    return add_info  # 这里返回了higher_order_func中的子函数add_info,也就是返回了add_info的内存地址


# 1. 这里我们将函数名func作为形参传入
# 2. higher_order_func的返回值为add_info的函数名,
# 3. 所以higher_order_func(func)相当于执行了higher_order_func函数并得到了返回值add_info, 得到了add_info函数
# 4. 那么higher_order_func(func)()就相当于add_info(), 也就是说我们执行了add_info()
func = higher_order_func(func)

func()  # 这里实际相当于执行了higher_order_func(func)()

输出如下:

13:16:30 开始运行程序
程序主体正在运行中……
13:16:35 程序运行结束

  这里面的逻辑相对是比较绕的,我们用下图做说明:
在这里插入图片描述
  从上面这个图里面我们可以看到,我们已经解决了我们遇到的问题。
  我们在高阶函数中定义了一个嵌套函数实现一个装饰器功能,实现要点如下:

  1. 封装想要添加的功能代码
  2. 调用作为参数传入的函数名
  3. 返回嵌套函数的函数名

四、装饰器类型

4.1 普通装饰器

  其实根据上述两个章节的介绍我们已经完成的简单的装饰器,就是高阶函数+嵌套函数 = 装饰器函数。但是Python给了我们一种更便捷、规范的方式来使用它。
  我们认为上述代码中下述两行比较多余,我还得对func = higher_order_func(func)赋值一下使用。

func = higher_order_func(func)

func()  # 这里实际相当于执行了higher_order_func(func)()

  所以我们用@decorator来替换上述赋值方式。具体代码如下:

import time


def higher_order_func(input_func):
    def add_info():
        print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
        input_func()
        print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

    return add_info


@higher_order_func  # 此处为Python语法糖,就相当于func函数名作为参数传给了名为higher_order_func的装饰器
def func():
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


func()

输出如下

13:14:55 开始运行程序
程序主体正在运行中……
13:15:00 程序运行结束

  感兴趣的人可以按照如下方式来单步执行代码,来看代码运行的步骤和执行逻辑。
在这里插入图片描述

4.2 被装饰函数带参数

  我们有些时候被装饰函数是要带参数的例如def func(name)这样我们就需要对上述代码做出改变:

import time


def higher_order_func(input_func):
    def add_info(*args, **kwargs):  # 可以传入任意形式的参数
        print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
        input_func(*args, **kwargs)  # 将参数解包再传给input_func
        print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

    return add_info


@higher_order_func  # 此处为Python语法糖,就相当于func函数名作为参数传给了名为higher_order_func的装饰器
def func(name):
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    print(f'传入参数为{name}')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


func('Tom')  # 传入参数'Tom',由于实际相当于在执行add_info(),那么参数实际传入给了add_info()函数

输出如下:

13:35:27 开始运行程序
程序主体正在运行中……
传入参数为Tom
13:35:32 程序运行结束

  我们再详细的对其解读一下,其中我们用到了(*args, **kwargs)写法,我们可以参考Python ——*号的常用用法来学习。如果不想看就记住这种固定的搭配。
在这里插入图片描述

4.3 装饰器本身带参数

   有些时候我们需要对装饰器本身进行传参,来定义装饰器内部的细微功能,举例上述代码中我们只想打印结束时间,不想打印开始时间。
   我们先分析一下我们想得到的代码
在这里插入图片描述
根据上述结论,我们将在add_info外面再套一层代码。

import time


def higher_order_func(enable_start_info=True, enable_stop_info=True):
    def receive_func_name(input_func):
        def add_info(*args, **kwargs):
            if enable_start_info:  # 使用了装饰器传进来的参数
                print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
            input_func(*args, **kwargs)
            if enable_stop_info:  # 使用了装饰器传进来的参数
                print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')

        return add_info
    return receive_func_name


@higher_order_func(enable_start_info=False, enable_stop_info=True)  # 此处对其传参启用具体功能
def func(name):
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    print(f'传入参数为{name}')
    time.sleep(5)
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


func('Tom')  # 传入参数'Tom',由于实际相当于在执行add_info(),那么参数实际传入给了add_info()函数

输出:

程序主体正在运行中……
传入参数为Tom
14:06:21 程序运行结束

   上面结果符合我们的要求,没有输出开始时间。下面我们再来分析一下他的工作流程。
在这里插入图片描述
   流程比较复杂我们可以设置断点来分步查看。

4.4 被装饰函数带返回值

  如果我们func函数有返回值,我们可以直接复制给一个变量,在add_info函数中返回。

import time


def higher_order_func(enable_start_info=True, enable_stop_info=True):
    def receive_func_name(input_func):
        def add_info(*args, **kwargs):
            if enable_start_info:  # 使用了装饰器传进来的参数
                print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
            input_func_return_value = input_func(*args, **kwargs)
            if enable_stop_info:  # 使用了装饰器传进来的参数
                print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')
            return input_func_return_value
        return add_info

    return receive_func_name


@higher_order_func(enable_start_info=False, enable_stop_info=True)  # 此处对其传参启用具体功能
def func(name):
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '开始运行程序')
    print('程序主体正在运行中……')
    print(f'传入参数为{name}')
    time.sleep(5)
    return 'func_return_value'
    # 想在此处加上 print(time.strftime('%H:%M:%S', time.localtime()), '程序运行结束')


print(func('Tom'))  # 由于实际相当于在执行add_info(),那么我们打印的就是add_info的返回值,上面我们把input_func的返回值传递给了add_info的返回值

输出如下:

程序主体正在运行中……
传入参数为Tom
14:23:09 程序运行结束
func_return_value

  简单分析一下:
在这里插入图片描述


参考文章

  1. Python开发编程高级进阶教程
  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值