高阶python | 装饰器

news2024/11/26 10:41:24

python版本:3.10.0

在学习装饰器前先了解一下闭包

阿-岳同学【python技巧060】形象理解闭包,玩转闭包

通过视频首先可以了解到主要的三个知识点

  1. 闭包是嵌套结构
  2. 内层函数有调用外层函数的变量为闭包,同时内层函数是闭包函数(所以闭包是函数,函数不一定是闭包)
  3. 内层函数可以调用外层函数的变量,而外层函数不能访问内层函数的变量
  4. 内层函数对外层函数中变量的值进行修改的时候需要使用nonlocal关键词(仅调用不需要该关键词直接使用)
  5. 对闭包的调用时对外层变量进行的是深拷贝

这是一个简单的闭包案例

def outer_func(x):
    def inner_func(y):
        return x + y

    return inner_func


add_5 = outer_func(5)
print(add_5(3))  # 输出 8
print(add_5(6))  # 输出 11

闭包结构中,外层函数先接一个x的值,在下方调用时传参为5,所以在闭包结构中x=5,然后进入内层函数(闭包函数),对内层函数调用时传参为3,所以在闭包结构中y=3,而后内层返回x+y的结果值给外层,外层返回内层返回的结果值给调用的print函数输出到终端。而后的y传6原理同样。

然后来讲装饰器

首先有一个普通的函数

def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

这是一个有双层嵌套循环的函数,接收n为外层循环的范围,内层循环两千次

count_nums(33_0000)
print('运行结束')

我们调用这个函数并且把外层函数的范围n传入函数

运行

然后等啊等,等了大概6秒才结束运行

运行结束

这样等了这么久,那么就好奇这个函数具体的运行时间有多久,但又不想破坏这个函数,此时我希望为这个函数加上一个计算运行时间的装饰器来计算运行时间

首先构造一个装饰器

装饰器是在闭包的基础上发展的结构,但其外层函数接收的不再是接收简单的数值,而是以一个函数对象打包接收

首先计算时间必要的time包是必要导入的

import time

然后构造装饰器

def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper

要让外层函数接收被装饰的函数,然后将被装饰的函数的调用夹在t1和t2之间作为闭包函数的内层函数,同时把内层函数接收到的传参传给被修饰函数,将被修饰函数的对象赋值给ret_vsl并在内层函数的结尾返回给外层,外层函数再返回给调用的地方

在原先调用被装饰的函数前,被装饰后的函数与装饰前的函数已经是两个函数,所以为保证不影响调用被装饰的函数和装饰器中的功能,我们将装饰器装饰后的新函数的对象同名赋值给老函数名

count_nums = make_timer(count_nums)

此时再调用该函数名,其功能已经被装饰器装饰,旧名新用

完整代码如下:

import time


def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper


# @make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass


count_nums = make_timer(count_nums)

count_nums(33_0000)
print('运行结束')

Time elapsed was 6.586411237716675
运行结束

而在python中,复杂的旧名新用的那行代码,可以用语法糖代替

@make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

语法糖“@make_timer”的意义就是告诉python我在被装饰函数后有一行

count_nums = make_timer(count_nums)

语法糖版本:

import time


def make_timer(func):  # 装饰器
    def wrapper(*args, **kwargs):
        t1 = time.time()
        ret_val = func(*args, **kwargs)
        t2 = time.time()
        print('Time elapsed was', t2 - t1)
        return ret_val

    return wrapper


@make_timer
def count_nums(n):
    for i in range(n):
        for j in range(2000):
            pass

count_nums(33_0000)
print('运行结束')

Time elapsed was 6.771592855453491
运行结束

 

再来一个加强的小例子来强化理解

首先看完整代码

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func


def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2


func = outer_func(native_func)  # 将装饰后的函数重新命名为被装饰的函数

word1, word2 = func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = func(num1=2, num2=4)
print(word1)
print(word2)

调用inner_func
调用native_func
5
6

调用inner_func
调用native_func
6
9

详细讲解:

首先看装饰器部分

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func

 外层接收函数,内层接收元组或字典,然后为了看调用顺序对控制台输出标记,然后对内层的变量进行赋值<该部分不懂的请看补充1>,然后先对a和b进行一次加数,然后再把a和b的新值传参给被装饰函数并调用后返回

(整个装饰器的过程可以想象成一个小箱子放进了一个大箱子套的中箱子里,这个过程就是装饰,当你拆快递时,大箱子打开是中箱子,中箱子打开就是被装饰的小箱子,小箱子里是你想要的结果,拆箱子的过程就是返回顺序也是机器运行顺序,其中的变量和传参就是就是开始的+三个箱子中的a小球和b小球的数量)

然后是被装饰函数的主体

def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2

这个就没什么好说的了,先加一行打印标记,各自加一后返回结果

func = outer_func(native_func)

为了便于和装饰器的部分连贯,这里没有使用语法糖代替也没有使用原名,便于理解

word1, word2 = func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = func(num1=2, num2=4)
print(word1)
print(word2)

然后给func传参,对于这两地方,他们合并后就是这个样子

word1, word2 = outer_func(native_func)(1, 1)  

outer_func(native_func) 指向 inner_func 并传入参数(1,1)分别赋值给a和b,其中的func就是native_func函数

我们可以利用__name__来打印一下装饰器内形参func的函数对象的名字是什么来证实上面的总结

修改装饰器部分的代码:

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        print(func.__name__)  # 是这一行哦
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func

运行: 

调用inner_func
native_func      <<<<<看这里
调用native_func

...后面省略

实际使用过程中,使用语法糖即可,完整代码如下

def outer_func(func):
    def inner_func(*args, **kwargs):
        print('调用inner_func')
        print(func.__name__)
        a, b = args if args else kwargs.values()
        a += 3
        b += 4
        return func(a, b)

    return inner_func


@outer_func
def native_func(num1, num2):
    print('调用native_func')
    num1 += 1
    num2 += 1
    return num1, num2


word1, word2 = native_func(1, 1)
print(word1)
print(word2)
print()

word1, word2 = native_func(num1=2, num2=4)
print(word1)
print(word2)

然后为了看装饰器是不是深拷贝,我们把上面的代码进行修改,全代码如下

def outer_func(func):
    a_list = [[], []]

    def inner_func(*args, **kwargs):
        a_list[0].append(111)
        return func(a_list)

    return inner_func


@outer_func
def list1_func(a_list):
    return a_list


@outer_func
def list2_func(a_list):
    return a_list


list1_func()
list2_func()
list1_func()
list2_func()
list1_func()
list1_func()
print('list1_func调用5次:', id(list1_func), list1_func(), sep=' ')
print('list2_func调用3次:', id(list2_func), list2_func(), sep=' ')

运行:

list1_func调用5次: 3042185555984 [[111, 111, 111, 111, 111], []]
list2_func调用3次: 3042185556128 [[111, 111, 111], []]

 事实证明,装饰器和闭包一样具有深拷贝的特性

也可以证明,如果有代码复用的部分,完全可以封装为一个装饰器,这样不但可以减少整体项目的代码量,同时还能方便其他程序员的语法糖使用

补充部分

1.python的快速赋值

python与其他需要变量声明的开发语言不同的是,python是先赋值后识别类型的开发语言,对于多个变量赋值可以采用以下方式

a, b = 2, 3
print(a)
print(b)

2
3

或者

a, *b = 2, 3, 4, 5, 6
print(a)
print(b)

2
[3, 4, 5, 6]

也可以

a, b = (2, 3)
print(a)
print(b)

2
3

args在上面第二个例子中的第一组的*args的结果是一个元组:(1, 1)

kwargs在第二个例子中的第二组的**kwargs的结果是一个字典:{'num1': 2, 'num2': 4}

kwargs.values()的结果是:dict_values([2, 4])

2.python的装饰器与继承的区别?

Python中的装饰器和继承都是以不同的方式扩展类的能力。

装饰器是一种可调用的对象,它接受一个函数作为参数,并返回一个新的函数。装饰器可以用来修改或者增强函数的行为。通过使用装饰器,
可以将一些通用的功能封装到可重用的装饰器中,从而减少代码冗余。

继承是Python中的一种面向对象编程技术,它被用来创建新的类,这些新的类继承了现有的类的属性和方法。
通过继承,子类可以重写或扩展父类的方法,从而实现特定的需求。

装饰器和继承的主要区别如下:

1. 作用对象不同:装饰器作用于函数、方法或者类,而继承只适用于类。

2. 能力扩展方式不同:装饰器通过增强函数的行为来扩展其能力,而继承则是通过从现有类派生新类来扩展其能力。

3. 处理方式不同:装饰器是在运行时动态地修改函数的行为,而继承是在定义类时静态地确定新类的行为。

虽然装饰器和继承具有一些相似之处,但它们的主要目的和应用不同。在编写Python代码时,应根据具体需求选择最合适的技术来实现特定的功能。

3.python中的闭包是一种递归嘛?

不一定。

闭包和递归是两个不同的概念,尽管它们有时会同时使用。

闭包是一个函数和它的环境变量的组合体,它用于捕获函数定义时可见的状态。这个函数可以在定义它的范围之外执行,并且可以访问和修改这些状态。

递归是一种函数或算法调用自身的方式。它通常用于解决重复的问题,例如遍历树或列表。

在Python中,闭包常常用于创造一个带有状态的函数或者对象。而递归则是实现算法或者遍历数据结构的一种有效手段。 虽然在某些情况下,我们可以使用递归实现闭包,但是它们并不是同一概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/578336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

根据DataFrame1中指定列c1的每个值a1 从DataFrame2中指定列c2中的每个值a2 找到与a1最临近的值a2,进行所在行合并

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 根据DataFrame1中指定列c1的每个值a1 从DataFrame2中指定列c2中的每个值a2 找到与a1最临近的值a2,进行所在行合并 [太阳]选择题 关于以下代码说法错误的是&#xff1a; import pandas as pd…

新手应该如何快速上手MySQL数据库?

前言 数据库是计算机系统中用于存储、管理和检索数据的系统。它允许用户访问并管理数据&#xff0c;并具有可靠、可扩展和高效的特性。 文章目录 前言1. 数据库的相关概念1.1 数据1.2 数据库1.3 数据库管理系统1.4 数据库系统1.5 SQL 2. MySQL数据库2.1 MySQL安装2.2 MySQL配置…

我在CSDN的736个日子——两年纪念日“随想”

2021-05-21~2023-05-27&#xff0c;我在 CSDN 已有 736 个日子。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&#xff1a;https:/…

设计一:51单片机流水灯控制

目录 一、设计内容 二、硬件电路分析 三、仿真原理图 四、程序设计 五、仿真结果 六、思考题 作者有话说 一、设计内容 本次设计使用4个按键&#xff0c;当KEY1按下时&#xff0c;P0口所接的发光二极管&#xff08;D1~D8&#xff09;以100ms的时间间隔自下至上循环点亮3…

软考A计划-试题模拟含答案解析-卷六

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

在线Excel绝配:SpreadJS 16.1.1+GcExcel 6.1.1 Crack

前端&#xff1a;SpreadJS 16.1.1 后端&#xff1a; GcExcel 6.1.1 全能 SpreadJS 16.1.1此版本的产品中包含以下功能和增强功能。 添加了各种输入掩码样式选项。 添加了在保护工作表时设置密码以及在取消保护时验证密码的支持。 增强了组合图以将其显示为仪表图。 添加了…

chatgpt赋能python:Python成为行业中的主力之一

Python成为行业中的主力之一 Python作为一种高级编程语言&#xff0c;已经成为了行业中的主力之一。Python的功能强大&#xff0c;易于学习和使用&#xff0c;而且兼容性良好。在数据科学&#xff0c;人工智能&#xff0c;web开发等领域&#xff0c;Python已经成为了不可或缺的…

chatgpt赋能python:Python转化为数字:Python程序员必须知道的关键技能

Python 转化为数字&#xff1a;Python 程序员必须知道的关键技能 Python 是一种广泛使用的编程语言&#xff0c;不仅适用于数据科学、人工智能和机器学习领域&#xff0c;还可以用于一般的应用开发。在 Python 中&#xff0c;数字是最基本的数据类型之一&#xff0c;因此了解如…

arm3399主板-使用ubuntu20.04搭建LVS-DR(netplan)

目录 一、规划 1、网络拓扑 2、检查 二、配置设备 1、配置LVS 1.配置IP转发 2.清除防火墙 3.安装ipvsadm工具 4.配置VIP 5.netplan与NetworkManager介绍 6.添加LVS规则 1.清除防火墙 2.添加伪装IP 3.安装web服务 4. 修改内核参数&#xff0c;防止IP冲突 3、配置w…

Golang每日一练(leetDay0079) 最大正方形、完全二叉树节点数

目录 221. 最大正方形 Maximal Square &#x1f31f;&#x1f31f; 222. 完全二叉树的节点个数 Count Complete Tree Nodes &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/…

ThingsBoard教程(五五):规则节点解析 REST接口调用 REST API Call Node,发送邮件Send Email Node

REST接口调用 REST API Call Nod Since TB Version 2.0 调用外部 REST 服务器的 REST API。 配置: 端点 URL 模式 - 可以是静态字符串,也可以是使用消息元数据属性解析的模式。例如 ${deviceType}。请求方法 - GET、POST、PUT、DELETE头 - 请求头,头或值可以是静态字符串…

chatgpt赋能python:Python转换为日期:完整解析

Python 转换为日期&#xff1a;完整解析 日期和时间是计算机编程中非常重要的一部分&#xff0c;可以用来记录和管理各种信息&#xff0c;例如日程安排、用户活动、数据更新等等。在 Python 中&#xff0c;我们可以使用 datetime 模块来轻松地进行日期和时间的操作和转换。 什…

JavaScript之DOM案例练习

1. 循环精灵图 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible" …

SpringCloud Alibaba Sentinel

SpringCloud Alibaba Sentinel Sentinel 基础 官网 1 Github: https://github.com/alibaba/Sentinel 2 快速开始: https://sentinelguard.io/zh-cn/docs/quick-start.html 3 中文: https://github.com/alibaba/Sentinel/wiki/介绍 4 使用手册: https://spring-cloud-alib…

chatgpt赋能python:Python除数为0处理详解

Python除数为0处理详解 在Python编程中&#xff0c;最常见的问题之一是除数为0的错误。当我们尝试将一个数除以0时&#xff0c;Python会抛出ZeroDivisionError错误&#xff0c;这是一个常见的运行时错误。 为什么会发生除数为0的错误&#xff1f; 当我们尝试将一个数除以0时…

如何在华为OD机试中获得满分?Java实现【MVP争夺战】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1、题目描述2、输入描述3、输出描述…

1、Ovirt 开源虚拟化平台安装

ovirt官网 一、资源规划介绍 1.1、服务规划 ovirt版本 ovirt engine 4.3.10 ovirt node 4.3.10 ovirt.node01.opsvv.com 负责托管引擎服务 1.2、资源划分 1.2.1、节点划分 密码均为&#xff1a;12345678 Node02无法开启虚拟化&#xff0c;只演示加入集群节点使用 节点…

软考A计划-试题模拟含答案解析-卷四

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Netty概述及Hello word入门

目录 概述 Netty是什么 Netty的地位 Netty的优势 HelloWord入门程序 目标 pom依赖 服务器端 客户端 运行结果 入门把握理解 概述 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable hi…

VITS语音生成模型详解及中文语音生成训练

1 VITS模型介绍 VITS&#xff08;Variational Inference with adversarial learning for end-to-end Text-to-Speech&#xff09;是一种结合变分推理&#xff08;variational inference&#xff09;、标准化流&#xff08;normalizing flows&#xff09;和对抗训练的高表现力语…