当前位置: 首页 > 编程日记 > 正文

Python基础15-函数闭包与装饰器

目录

装饰器概念

装饰器的实现

修改被装饰函数的代码(非装饰器实现)

修改被装饰函数的调用方式(非装饰器实现)

装饰器的实现(不完整的实现level1)

装饰器实现的语法糖(不完整的实现level2)

函数闭包加上返回值和参数(基本完整的装饰器实现)

过渡内容

带参数的装饰器


装饰器概念

装饰器本质就是函数,功能是为其他函数添加附加功能。装饰器的原则如下

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

装饰器的实现

要实现装饰器,就是遵循装饰器的原则来实现装饰器,具体做法就是 装饰器=高阶函数+函数嵌套/闭包。

我们用一个例子来实现以下功能。有个函数foo,执行时间较长,我们在不修改foo函数的源代码、不修改foo函数的调用方式的前提下,显示foo函数执行的时间。这个需求就用装饰器来实现,我们一点一点接近装饰器的完整实现。

import timedef foo():"""模拟foo函数运行了3秒左右:return: """time.sleep(3)print('from foo')pass# foo函数的调用方式就是直接调用
foo()

修改被装饰函数的代码(非装饰器实现)

我们可以选择在foo函数开始执行的时候记下时间,在foo结束执行的时候再记下时间,从而得到foo函数的执行时间。但是这样就必须修改foo函数的源代码,这在大型项目里面很可能会引发连锁反应,导致其他调用的地方出错。

import timedef foo():"""模拟foo函数运行了3秒左右:return:"""start_time = time.time()time.sleep(3)print('from foo')stop_time = time.time()print("foo执行时间 %s" % (stop_time - start_time))pass# foo函数的调用方式就是直接调用
foo()
# from foo
# foo执行时间 3.0006065368652344

修改被装饰函数的调用方式(非装饰器实现)

我们还可以保持foo函数不修改,将foo作为参数传给装饰器函数timer,由timer来记录foo的执行时间。这个样保证了foo代码不变,但是foo函数的调用方式发生了变化。

import timedef timer(func):start_time = time.time()func()stop_time = time.time()print("running time: %s" % (stop_time - start_time))return funcdef foo():"""模拟foo函数运行了3秒左右:return:"""time.sleep(3)print('from foo')passtimer(foo)
# from foo
# running time: 3.013805627822876

装饰器的实现(不完整的实现level1)

有没有既不修改被装饰函数的源代码、又不修改被装饰函数的调用方式,这样两全其美的实现呢?那就是装饰器。装饰器函数:将被装饰函数作为入参,这就需要定义高阶函数;将附加了装饰功能的函数作为返回值返回,同时保证被装饰函数在装饰器里面不被调用,这就需要函数嵌套/闭包。最后,要保证调用方式不变,那么就将返回值赋给被装饰函数。

具体到被装饰函数foo和装饰器timer的例子。就是foo要作为timer的入参,timer里面定义嵌套函数wrapper,wrapper被调用时foo和修饰功能都被执行,将wrapper作为返回值返回,返回值付给foo。

import timedef timer(func):def wrapper():start_time = time.time()func()stop_time = time.time()print("运行时间 %s" % (stop_time - start_time))passreturn wrapperdef foo():"""模拟foo函数运行了3秒左右:return:"""time.sleep(3)print('from foo')passfoo = timer(foo)
foo()
# from foo
# 运行时间 3.012805223464966

装饰器实现的语法糖(不完整的实现level2)

上面的实现还有一个问题,那么就是每次都要经过装饰器返回值付给被修饰函数的过程。这个Python在语法层面予以解决。那就是把  @装饰器  放在被装饰函数定义的前一行,这样就装饰了。

import timedef timer(func):def wrapper():start_time = time.time()func()stop_time = time.time()print("运行时间 %s" % (stop_time - start_time))passreturn wrapper@timer
def foo():"""模拟foo函数运行了3秒左右:return:"""time.sleep(3)print('from foo')passfoo()
# from foo
# 运行时间 3.012805461883545

函数闭包加上返回值和参数(基本完整的装饰器实现)

上面实现的装饰器还不够完整。如果被装饰的函数还有入参和返回值,那么上面的实现方式是不行的。我们来解决这个问题。

首先,如果被装饰函数有入参,在执行装饰器返回的嵌套函数里面也要有相应的入参。为了保证装饰器适应性任意多个入参,这个嵌套函数应该有变长的参数,因此wrapper的入参是*args,**kwargs。调用func的时候也应当相应的加参数*args,**kwargs。注意,这两个*args,**kwargs的含义是不一样的。wrapper的参数是函数定义,回忆函数基础那篇博客变长参数的部分,这里的args和kwargs分别是元组和字典。func调用的地方是函数调用的入参,是解压序列,将args元组解压序列成位置参数,将kwargs解压序列成关键字参数。因此,在装饰器实现的时候,都应保证wrapper和func的参数是*args,**kwargs。

其次,如果被装饰函数有返回值,在执行装饰器返回的嵌套函数里面也要有相应的返回值。解决办法就是将返回值记录下来。在适当的地方返回。

import timedef timer(func):def wrapper(*args, **kwargs):  # 这里的*args, **kwargs是函数定义,表示可边长参数start_time = time.time()res = func(*args, **kwargs)  # 这里的*args, **kwargs是函数调用的入参,将wrapper的参数解压序列stop_time = time.time()print("运行时间 %s" % (stop_time - start_time))return respassreturn wrapper@timer
def foo(os, db, app):"""模拟foo函数运行了3秒左右:return:"""time.sleep(0.2)res = 'os->%s,db->%s,app->%s' % (os, db, app)print('from foo')return respassRES = foo('linux', db='mysql', app='apache')
print(RES)
# from foo
# 运行时间 0.20280051231384277
# os->linux,db->mysql,app->apache

过渡内容

这一部分为后面做铺垫,例子完全来自我所看视频教程的内容。需求是,在首页index、个人中心home和购物车shopping_car执行前进行身份验证,不能修改这三个函数的调用方式。

def index():print('welcome to front page')passdef home(name):print('welcome home%s' % name)passdef shopping_car(name):print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))passindex()
home('kevin')
shopping_car('kevin')
# welcome to front page
# welcome homekevin
# kevin's shopping car has [food,drink,toy]

用全局变量来模拟数据库记录和当前用户session。代码如下。

# 用户列表,模拟数据库存储用户信息
user_dic = [{'username': 'alice', 'password': '123'},{'username': 'kevin', 'password': '123'},{'username': 'bob', 'password': '123'},{'username': 'charlie', 'password': '123'},
]# 当前用户,模拟session
curr_user = {'username': None, 'login': False}def auth_func(func):def auth_check(*args, **kwargs):# 如果有session,直接执行功能if curr_user['username'] and curr_user['login']:res = func(*args, **kwargs)return res# 否则验证身份username = input('username:').strip()password = input('password:').strip()for user in user_dic:if username == user['username'] and password == user['password']:# 身份验证成功,更新session,并执行功能curr_user['username'] = usernamecurr_user['login'] = Trueres = func(*args, **kwargs)return respass# 验证不成功,报错print('wrong username or password!')passreturn auth_check@auth_func
def index():print('welcome to front page')pass@auth_func
def home(name):print('welcome home, %s' % name)pass@auth_func
def shopping_car(name):print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))passindex()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# welcome home, kevin
# kevin's shopping car has [food,drink,toy]

带参数的装饰器

接上面过渡内容,如果我们的验证方式有不同,index用数据库,home和shopping_car用其他的验证方式,那怎么办呢?可以用带有参数的装饰器。我们在上面auth_func外再包一层auth,再调用装饰器的时候,调用auth执行的结果。代码如下。

# 用户列表,模拟数据库存储用户信息
user_dic = [{'username': 'alice', 'password': '123'},{'username': 'kevin', 'password': '123'},{'username': 'bob', 'password': '123'},{'username': 'charlie', 'password': '123'},
]# 当前用户,模拟session
curr_user = {'username': None, 'login': False}def auth(auth_type='filedb'):def auth_func(func):def auth_check(*args, **kwargs):# 这里通过auth_type可以做更多的选择if auth_type == 'filedb':# 如果有session,直接执行功能if curr_user['username'] and curr_user['login']:res = func(*args, **kwargs)return res# 否则验证身份username = input('username:').strip()password = input('password:').strip()for user in user_dic:if username == user['username'] and password == user['password']:# 身份验证成功,更新session,并执行功能curr_user['username'] = usernamecurr_user['login'] = Trueres = func(*args, **kwargs)return respass# 验证不成功,报错print('wrong username or password!')elif auth_type == 'ldap':print('ldap check')res = func(*args, **kwargs)return reselse:print('what the fuck!')passreturn auth_check# 将auth_func返回return auth_func# @ auth调用的结果
# 也就相当于@auth_func
@auth()
def index():print('welcome to front page')pass# @ auth调用的结果
# 也就相当于@auth_func
@auth(auth_type='ldap')
def home(name):print('welcome home, %s' % name)pass# @ auth调用的结果
# 也就相当于@auth_func
@auth(auth_type='kerbers')
def shopping_car(name):print('%s\'s shopping car has [%s,%s,%s]' % (name, 'food', 'drink', 'toy'))passindex()
home('kevin')
shopping_car('kevin')
# username:kevin
# password:123
# welcome to front page
# ldap check
# welcome home, kevin
# what the fuck!

相关文章:

python 全栈开发,Day132(玩具管理页面,控制玩具通讯录,基于请求的好友关系建立)...

先下载github代码,下面的操作,都是基于这个版本来的! https://github.com/987334176/Intelligent_toy/archive/v1.5.zip 注意:由于涉及到版权问题,此附件没有图片和音乐。请参考链接,手动采集一下&#xff…

iOS技术篇1-CocoaPods

iOS技术篇1-CocoaPods 上一篇: 目录 下一篇:ios技术篇2-CoreData

计算机艺术未来发展趋势,计算机技术对现代艺术设计的影响

摘 要 在当今,计算机技术已经覆盖到了全球生活的各个领域,毫不夸张地说,计算机技术已经成为世界上最重要的技术之一。计算机技术对于某些行业和领域的冲击是巨大的,甚至是革命性的改变,它能够使很多的活动变得更为便捷…

【JZOJ5064】【GDOI2017第二轮模拟day2】友好城市 Kosarajo算法+bitset+ST表+分块

题面 在Byteland 一共有n 座城市,编号依次为1 到n,这些城市之间通过m 条单向公路连接。 对于两座不同的城市a 和b,如果a 能通过这些单向道路直接或间接到达b,且b 也能如此到达a,那么它们就会被认为是一对友好城市。 By…

Python基础16-模块与包基础01

目录 初识模块和包 Python常用的内置模块 关键字import和from import、from查找的路径 如何调用 __name__与模块执行 __name__的用法(单元测试) 初识模块和包 我们把功能相近或相关的py文件组成模块,这样分开写代码便于维护&#xff0c…

配置用户通过Telnet登录设备的身份认证(AAA本地认证)

背景信息 用户通过Telnet登录设备时,设备上必须配置验证方式,否则用户无法成功登录设备。设备支持不认证、密码认证和AAA认证三种用户界面的验证方式,其中AAA认证方式安全性最高。 采用AAA本地认证方式实现用户通过Telnet登录设备的身份认证&…

【自考】信息系统开发与管理(二)——章节详读

自考的第二阶段结束了,这一阶段是对书的详读过程。每章节读完,画一个导图。将其总结成一张网。织网的过程就是思考的过程。织网不断进行中……!宏观方面:1~3章第一章 管理信息系统导论在研究一…

ios技术篇-CoreData

ios技术篇-CoreData 上一篇: iOS技术篇-CocoaPods 目录 下一篇:

中山大学计算机学院运动会,喜讯!我院获2019中大校运会教工组团体第二名

11月2日,中山大学2019年运动会在南校园举行,来自全校68个院系、附属医院、部门共3200余名师生参加比赛。由37名职工运动员组成的中山七院代表队参加教工组田径赛、趣味田径及球类等全部15项比赛,经过激烈角逐,最终以团体总分173分…

Python基础17-模块与包基础02、常用模块之time、random

目录 名字冲突与避免 设置BASE_DIR保证程序能找到模块位置 time random 名字冲突与避免 在test.py里写下面一段代码,用正则表达式包re进行匹配,匹配出123开头的字符。如果我们在test.py同级写一个re.py,那么Python解释器在进行导入时就会…

Hadoop学习笔记(1) ——菜鸟入门

Hadoop学习笔记(1) ——菜鸟入门 Hadoop是什么?先问一下百度吧: 【百度百科】一个分布式系统基础架构,由Apache基金会所开发。用户能够在不了解分布式底层细节的情况下。开发分布式程序。充分利用集群的威力进行快…

HTTP协议简介

HTTP协议HTTP协议简介 超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。 HTTP的发展是由蒂姆伯纳斯-李于1989年在…

计算机组成原理读写周期波形图,第3章存储器层次结构-1讲述.ppt

第3章存储器层次结构-1讲述计算机组成原理 * 计算机组成原理 ——存储器层次结构(1) 2016-3-18 几个基本概念 1、存储器:计算机系统中的记忆设备,用来存放程序和数据。 2、存储元:存储器的最小组成单位,用以存储1位二进制代码。 3…

iOS架构篇-4 架构模式MVVM

iOS架构篇-4 架构模式MVVM MVVM原理MVVM 登录例子View:ViewModel:Model:如果觉得可以就点个👍吧,欢迎粉丝收藏,土豪打赏,您的关注就是我们创作的动力!读者有什么想看的相关技术篇章,欢迎评论留言!QQ交流群:908058499MVVM原理 #mermaid-svg-s6n4t9QkR9OeNy45 .label{fon…

CV00-01-开篇与环境搭建

目录 Intro 环境搭建 TensorFlow搭建 PyTorch搭建 PaddlePaddle搭建 Intro 从今天起学习CV,为期6个月,以三个真实项目为背景学习CV。 目前是第一个项目——车道线检测。时间两个月(共8周),每周五、周日晚上在线…

Spring MVC环境中的文件上传功能实现

在实际开发过程中,尤其是web项目开发,文件上传和下载的需求的功能非常场景,比如说用户头像、商品图片、邮件附件等等。其实文件上传下载的本质都是通过流的形式进行读写操作,而在开发中不同的框架都会对文件上传和下载有或多或少的…

iOS架构篇-5 CI/CD(持续集成、持续交付、持续部署)

iOS架构篇-5 CI/CD(持续集成、持续交付、持续部署) CI CI是指持续集成,代码的更新会定期自动构建、测试并合并到公共仓库中,方便多分支时解决冲突问题 CD CD是指持续交付和/或持续部署,开发人员改动代码会自动测试提交到仓库,运维实施人员将其部署到生产环境中,方便部…

计算机函数模式的用处是啥,请问怎么理解计算机中的函数?

你的理解有点外行看热闹的意思,呵呵。代码本身就是抽象的,所以“计算机中的函数是一种对代码进行抽象的方式”不能说不对,但是也和没说一样。至于“我们使用抽象出来的函数,而不用关心函数里面的代码是如何组织的”,只…

CV00-03-CV基本操作2

基本操作2 Similarity Transform相似变换 Similarity Transform相似变换:图像形状大小不变,位置发生变化。比如:做平移、旋转。相似变换具有保角性、保比例性,经过相似变换以后原有的角度和比例保持不变。确定一个相似变换矩阵需…

[LeetCode] [C++] 第一轮刷题总结(持续更新~~~)

LeetCode 解题报告 LC_1_解题报告LC_2_解题报告LC_3_解题报告LC_4_解题报告LC_5_解题报告LC_6_解题报告LC_7_解题报告LC_206_解题报告LC_237_解题报告LC_344_解题报告 LeetCode 1. Two Sum 解题思路:两次循环遍历数组,找到两个元素和等于target 注意点&…

Android Studio 在项目中引用第三方jar包

在Android Studio项目中引用第三方jar包的方法: 步骤: 1、在build.gradle文件中添加如下代码: 备注:要添加在Android作用域下 sourceSets {main {jniLibs.srcDirs [libs]}} 点击【Sync Now】,会生成jniLibs文件夹 找到…

android专栏目录

android专栏目录 Android基础篇 android专题-数据库room android专题-蓝牙扫描、连接、读写 Android专题-常用第三方框架 Android高级篇 Android架构篇-1 项目组织架构 Android架构篇-2 国际化多语言 Android架构篇-3 网络接口封装 Android架构篇-4 架构模式MVVM Android架…

东北大学计算机分数线2017,东北大学2017年本科一批录取分数线(全国)

东北大学2017年全国各省各批次集中录取时间为7月6日-27日,在各省录取结束的分批次分科类录取最低分将在本页面持续更新公布,考生录取结果可通过关注东北大学招生办官方微信公众号(neuzs-1923)录取专区查询,最终录取结果请以考生收到的录取通知…

CV00-04-卷积

卷积概念 由于不好进行文字描述(懒),我直接推荐一个博客图像卷积,讲解图像卷积的概念。 图像卷积操作(convolution),或称为核操作(kernel),是进行图像处理的…

unity项目build成webgl时选择生成目录(解决方法)

在unity里点击File>>Build Settings...>>勾选你要生成的Scenes>>选择webgl>>后面Development Build不要勾选:点击build后会让你选择生成的目录,此处要慎重选择,否则会报错! 不要选择到项目所在目录&#…

STL中的nth_element()方法的使用

STL中的nth_element()方法的使用 通过调用nth_element(start, startn, end) 方法可以使第n大元素处于第n位置(从0开始,其位置是下标为 n的元素),并且比这个元素小的元素都排在这个元素之前,比这个元素大的元素都排在这个元素之后&…

Android架构篇-2 国际化多语言

Android架构篇-2 国际化多语言 实现功能: 1.默认采用系统语言 2.语言切换后实时生效 3.支持中英文 4.我的->设置->切换语言 思路:app首次初始设置为系统语言,用户在app内切换语言时发送语言切换事件,刷新所有页面 在AppBaseActivity、AppBaseFragment通过EventB…

齐鲁工业大学计算机读研,齐鲁工业大学考研难吗

齐鲁工业大学考研难吗?1、齐鲁工业大学考研难度算是比较容易。不在大学考研难度排名前100名单之内。2、考研究生难易程度还是看招生院校的地域、名气、排名等因素,生源不同,竞争力度也不同。发达地区特别是像北京,上海这样的大城市…

Python基础18-常用模块之os、sys、json、pickle、shelve、xml、re、logging、configparse、hashlib等

目录 os、os.path sys json pickle、shelve、xml、re、logging、configparse、hashlib未完待续…… os、os.path Python的os模块里面定义了常用的路径、文件操作。 os.curdir # curdir相对路径的当前路径“点” os.pardir # pardir相对路径的父目录“点点”。 os.sep …

剑指offer 重建二叉树 python

题目描述 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 样例 输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6} 返回二叉树头节点想法: 使用递归,既…