python装饰器调用顺序_聊一聊Python装饰器的代码执行顺序

为什么写这篇文章?

起因是QQ群里边有人提了一个问题:之前导入模块只需要1~2秒,为什么现在变成需要2~3分钟?

我的第一感觉是:是不是导入的模块顶层代码里边,做了什么耗时的事情。隔了一天,他的问题解决了,下边是按照他的代码写了一个类似的例子:

import time

def set_log(func):

def wrap(*args, **kwargs):

return func(*args, **kwargs)

time.sleep(4)

return wrap

@set_log

def demo():

pass

为什么导入这个模块的时候,会运行time.sleep(4),明明没有调用demo函数呀?这就要从Python装饰器代码的执行顺序说起了。

简单介绍下装饰器

在正式开始之前,先简单科普一下Python的装饰器,装饰器可以对已有的函数,添加额外的功能,甚至于完全改变函数的执行效果。举个例子,现在想统计几个函数的执行耗时,函数是这样的:

import time

import random

def a_func():

time.sleep(random.randint(1, 5))

当然,我们可以这么写

def a_func():

start_time = time.time()

time.sleep(random.randint(1, 5))

print("cost time: {}".format(time.time() - start_time))

这样带来的问题是代码的可维护性不佳,尤其你有多个函数需要计算耗时的时候,万一某天突然想去掉这些统计代码呢~

所以像这种有切面需求的场景,装饰器是一个非常漂亮的设计。

def cost_time(func):

def wrap(*args, **kwargs):

start_time = time.time()

result = func(*args, **kwargs)

print("cost time: {}".format(time.time() - start_time))

return result

return wrap

@cost_time

def a_func():

time.sleep(random.randint(1, 5))

只需要对统计耗时的函数挂上一个装饰器,结果就自动出来,无需改动之前的代码,非常方便。

Python也支持带参数的装饰器,比如刚刚的cost_time加入一个报警机制,如果函数执行耗时大于1秒,就发出警告。

def cost_time(warn=1):

def wrap(func):

def _wrap(*args, **kwargs):

start_time = time.time()

result = func(*args, **kwargs)

cost = time.time() - start_time

print("cost time: {}".format(cost))

if cost > warn:

print("warning, cost time is {} !!!".format(cost))

return result

return _wrap

return wrap

@cost_time()

def a_func():

time.sleep(random.randint(1, 5))

a_func()

执行结果:

cost time: 3.0002505779266357

warning, cost time is 3.0002505779266357 !!!

Python装饰器代码的执行顺序

回到我们的主题,首先把刚刚的例子加入一些打印:

import time

print("准备编写装饰器")

def set_log(func):

print("装饰器顶层代码")

def wrap(*args, **kwargs):

print("装饰器内层代码")

return func(*args, **kwargs)

# time.sleep(4)

print("准备返回wrap对象")

return wrap

print("准备编写demo函数")

@set_log

def demo():

print("正在运行demo函数")

if __name__ == '__main__':

print("准备运行demo函数")

demo()

运行结果是:

准备编写装饰器

准备编写demo函数

装饰器顶层代码

准备返回wrap对象

准备运行demo函数

装饰器内层代码

正在运行demo函数

所以在运行demo函数之前,已经做了:

准备编写装饰器

准备编写demo函数

装饰器顶层代码

准备返回wrap对象

也就是说,就算你没有运行demo函数,只是导入了这个模块,上边的这4件事情,都是会一一执行的。

是不是有点懵?

让我们从头开始,梳理一遍这个过程。

Python的代码是从上往下依次执行的,所以当你导入这个模块,第一句运行的代码就是

import time

然后就来到了

print("准备编写装饰器")

接着是来到了set_log装饰器函数的定义

def set_log(func):

需要注意的时候,在这里Python只运行了函数的定义语句,对于函数内部的执行,是直接跳过去的,并没有运行。

继续往下,来到了

print("准备编写demo函数")

此时重点来了,到了demo函数的定义了

@set_log

def demo():

print("正在运行demo函数")

因为代码从上往下依次运行的机制,Python解释器首先到了@set_log这句代码,@这个符号是Python提供的语法糖,它本质上是为了简化了装饰器的写法,上边的写法等于

def demo():

print("正在运行demo函数")

demo = set_log(demo)

于是Python开始执行set_log装饰器,来完成对demo函数的修饰。

def set_log(func):

print("装饰器顶层代码")

def wrap(*args, **kwargs):

print("装饰器内层代码")

return func(*args, **kwargs)

# time.sleep(4)

print("准备返回wrap对象")

return wrap

首先来到的是

print("装饰器顶层代码")

然后是装饰器内部wrap函数的定义,同样是,只运行了定义语句,跳过函数的内部执行代码

def wrap(*args, **kwargs):

然后来到了打印“准备返回wrap对象”,以及返回wrap对象,要注意,在返回了wrap函数对象后,此时demo函数,其实已经被替换成了wrap函数对象。

print("准备返回wrap对象")

return wrap

完成了对demo函数的修饰后,代码也来到了最后的调用demo函数的部分

if __name__ == '__main__':

print("准备运行demo函数")

demo()

新的重点来了~

上边说到,在装饰器内部返回了wrap对象后,demo已经被替换成了wrap函数对象了。

也就说说,运行 demo(),其实就是运行wrap()

def wrap(*args, **kwargs):

print("装饰器内层代码")

return func(*args, **kwargs)

所以代码来到了wrap的函数内部,首先当然就是打印了“装饰器内层代码”。接下来是

return func(*args, **kwargs)

这里的func是不是很眼熟?我们回去看看set_log装饰器的定义:

def set_log(func):

print("装饰器顶层代码")

def wrap(*args, **kwargs):

print("装饰器内层代码")

return func(*args, **kwargs)

# time.sleep(4)

print("准备返回wrap对象")

return wrap

func就是我们一开始传给set_log装饰器修饰的demo函数,还记得上边写的,装饰器的两种写法吗?

@set_log

def demo():

pass

# 等同于:

def demo():

pass

demo = set_log(demo)

于是代码进入到了demo函数的内部去了~

def demo():

print("正在运行demo函数")

执行完毕,最终搞定,一个装饰器的代码执行顺序就是这么走过来的。

最后,再来一个多重+带参数的装饰器的复杂一点的例子~

print("准备编写装饰器")

def set_log_first(func):

print("set_log_first装饰器顶层代码")

def wrap(*args, **kwargs):

print("set_log_first装饰器内层代码")

return func(*args, **kwargs)

print("set_log_first准备返回wrap对象")

return wrap

def set_log_second(times=1):

print("set_log_second装饰器顶层代码")

def wrap(func):

print("set_log_second装饰器中间层代码")

def _wrap(*args, **kwargs):

print("set_log_second装饰器内层代码")

return func(*args, **kwargs)

print("set_log_second准备返回中间层的_wrap对象")

return _wrap

print("set_log_second准备返回顶层的wrap对象")

return wrap

print("准备编写demo函数")

@set_log_first

@set_log_second()

def demo():

print("正在运行demo函数")

if __name__ == '__main__':

print("准备运行demo函数")

demo()

输出是~

准备编写装饰器

准备编写demo函数

set_log_second装饰器顶层代码

set_log_second准备返回顶层的wrap对象

set_log_second装饰器中间层代码

set_log_second准备返回中间层的_wrap对象

set_log_first装饰器顶层代码

set_log_first准备返回wrap对象

准备运行demo函数

set_log_first装饰器内层代码

set_log_second装饰器内层代码

正在运行demo函数

这里理解的重点就是,下边的两个写法是等价的

@set_log_first

@set_log_second()

def demo():

print("正在运行demo函数")

# 等价于

demo = set_log_first(set_log_second()(demo))

装饰器是不是很好玩呢?

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

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

相关文章

centos7 安装cacti

1 cacti运行环境准备   cacti需要phpapachemysqlsnmpRRDTool,以及cacti本身。cacti本体是用php开发的网站,通过snmp对远端设备信息进行采集。apachemysqlphp在以前已经做过了      这里只对剩余的部分进行安装。 2 安装snmp    yum install -y n…

python第三方库-基础

1.python社区 python有一个全球社区,提供了超过十三万个涵盖各种领域应用的第三方库,该社区可通过 http://pypi.org/ 来访问。PyPI(Python Package Index)是python包的索引,学会检索并利用PyPI,找到合适的第…

python折线图matplotlib库_Python如何使用内置库matplotlib绘制折线图

这篇文章主要介绍了Python如何使用内置库matplotlib绘制折线图,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下环境准备:需要安装matplotlib,安装方式:pip install matplotlib…

linux下zabbix安装

1本人用的是apachemysqlphp 2下载zabbix软件包,官网下载 https://sourceforge.net/projects/zabbix/files/ZABBIX Latest Stable/2.2.23/zabbix-2.2.23.tar.gz/download 上传到 var/www/html下 3cd /var/www/html #进入软件包下载目录 tar zxvf zabbix-2.2.23.tar.g…

java cas机制_Java CAS机制详解

CAS目的:在多线程中为了保持数据的准确性,避免多个线程同时操作某个变量,很多情况下利用关键字synchronized实现同步锁,使用synchronized关键字修可以使操作的线程排队等待运行,可以说是一种悲观策略,认为线…

「一本通 4.1 练习 2」简单题

题目描述 题目来源:CQOI 2006 有一个 n 个元素的数组,每个元素初始均为 0。有 m 条指令,要么让其中一段连续序列数字反转——0 变 1,1变 0(操作 1),要么询问某个元素的值(操作 2&…

定时器取数据时实时进来的数据_Redis-数据淘汰策略amp;持久化方式(RDB/AOF)amp;Redis与Memcached区别...

Redis与Memcached区别: 两者都是非关系型数据库。主要有以下不同: 数据类型: Memcached仅支持字符串类型。 redis支持:String,List,set,zset,hash 可以灵活的解决问题。 数据持久化: Memcached不支持持久化。 Redis采…

linux 下建立多个tomcat

第一步:复制,解压 将准备好的tomcat压缩包复制到你准备安装的目录,我的tomcat压缩包名字是tomcat.tar.gz,我的安 装目录是 /usr/java/tomcat 第二步:解压tomcat [rootaliServer tomcat]# tar -xvf tomcat.tar.gz 第三步&#xff…

java apply 函数_Js(Javascript)中的apply方法的使用

Function.apply(obj,args)方法能接收两个参数,简单说apply方法作用就是给类或方法中的this赋值。所以学会这个方法首先要知道this的作用。(this的用法可以看一下这个链接:http://www.cjavapy.com/article/8/ )obj:这个对象将代替Function类里…

linux iptables配置

1 iptables默认系统自带 setup 2重启防火墙 /etc/init.d/iptables restart 3接受端口 Vi /etc/sysconfig/iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT 4 #配置,禁止进,允许出,允许回环网卡 iptables -P I…

memcpy函数_[PART][BUG][MSVCRT][C][CCF NOI1097] 关于memcpy的坑

[Incompleted]CCF NOI1097 试题,本人的源码:Ubuntu Pastebin​paste.ubuntu.comUbuntu PastebinUbuntu Pastebin: SourceCodebyJulianDroid​paste.ubuntu.com满分代码:https://blog.csdn.net/tigerisland45/article/details/71038551​blog.…

Bugku杂项-convert

一进去就发现一堆二进制数,然后考虑怎么才能把这个和隐写扯上关系。首先,二进制我们肉眼就是看不懂再说什么的,这里就想到了转换,再联想上hex将原始数据转化为16进制。我们可以先把2进制转化为16进制,然后再放到hex上看…

tomcat:Cannot find /usr/local/tomcat1/bin/setclasspath.sh

首先看下报错代码: Cannot find /usr/local/tomcat1/bin/setclasspath.sh This file is needed to run this program这个可能是没有在 /etc/profile 中配置环境,这是第一种可能;如果是这种情况的话,可以这样做:vi /etc/profile 并…

在java中柱状图代码_我在java中编写了个柱状图,可运行了,我想让柱状图在JSP页面中显示,请问有什么方法么?谢谢。...

h1,h2 代表了柱形图的高度你可以这样试一试function createImgItem(count){var divdocument.createElement("");var imgdocument.createElement("");img.src"getCertReviewInfoImg.jspx?perCertId${perCertId}&reviewIndex"count;div.appen…

图解cacti简单使用

1登录 admin admin 2点击devices localhost 3进入配置保存 4保存 http服务要启动哦 5一步步做 6graph tree 7执行/usr/bin/php /var/www/html/cacti/poller.php 8如果时间设置错误去php.ini里面修改时间 YSTEM STATS: Time:0.4759 Method:cmd.php Processes:1 Threads:N/…

AFNetworking 3.0源码阅读 - AFURLResponseSerialization

这次来说一下AFURLResponseSerialization这个HTTP响应类。 定义了一个协议,该协议返回序列化后的结果。后续的AFHTTPResponseSerializer以及他的子类都遵循了该协议 该类内有很多子类定义,这里借用一张图来展示,之后一个一个来说。 我们先来看…

python3纵向输出字符串_Python 3.x 格式化输出字符串 % format 笔记

python格式化字符串有%和{}两种 字符串格式控制符.字符串输入数据格式类型(%格式操作符号)%%百分号标记%c字符及其ASCII码%s字符串%d有符号整数(十进制)%u无符号整数(十进制)%o无符号整数(八进制)%x无符号整数(十六进制)%X无符号整数(十六进制大写字符)%e浮点数字(科学计数法)%…

linux 下tomcat服务每天定时启动

1l先准备一个脚本 #!/bin/sh #./etc/profile export JAVA_HOME/usr/java/jdk1.6.0_45 sh /home/tomcat-bingchuang/bin/shutdown.sh sleep 60s sh /home/tomcat-bingchuang/bin/startup.sh 2放置到如上/home/ tomcat-bingchuang/bin/目录下 赋予777权限 并在linux里面设置…

Swordsman

ps&#xff1a;比赛的时候想到了做法&#xff0c;k次排序&#xff0c;然后每次消去能消的。。。然而这种做法是错误的&#xff0c;神奇的是测试案例中排在奇数的案例会WA&#xff0c;排在偶数的案例都过了&#xff0c;被注释的代码会T. #include<bits/stdc.h> #define UL…

java socket编程聊天室_Java Socket通信之聊天室功能

Java Socket通信之聊天室功能发布时间&#xff1a;2020-10-17 14:36:00来源&#xff1a;脚本之家阅读&#xff1a;73作者&#xff1a;LY_624本文实例为大家分享了Java Socket聊天室功能的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下Client.javaimport java.io.*;i…