Python——第四章:闭包(Closure)、装饰器(Decorators)
闭包:
本质, 内层函数对外层函数的局部变量的使用. 此时内层函数被称为闭包函数
1. 可以让一个变量常驻与内存,可随时被外层函数调用。
2. 可以避免全局变量被修改、被污染、更安全。(通过版本控制工具,将不同人所写的代码都整合的时候,避免出现问题)
def func():
a = 10
def inner():
print(a)
return a
return inner
ret = func()
代码定义了一个函数 func,它返回了另一个函数 inner。这种结构被称为闭包(closure),因为 inner 函数引用了在其外部定义的变量 a。在这里,a 是 func 函数的局部变量,但由于 inner 函数引用了它,a 的值在 inner 函数被调用时仍然是可用的。
这段代码可以实现的特殊效果:
1、因为迟迟没有使用ret()调用ret(inner函数),程序为了能够持续提供可用性,会将该段代码常驻于内存。不会被内存回收。
2、用函数来定义局部变量a,并且局部变量不会被后续函数外的代码操控、改变,仅可以被赋值后读取、打印。不能被其他全局变量修改。实现局部变量仅可以在本函数内部才可以被操作。a被保护起来了。
3、想调用这个局部变量a,可以随时调用ret()函数,使得局部变量既可以被调用,还不会被修改。
未来某一时刻,ret()调用了 func 函数并将其结果赋值给变量 ret。此时,ret 包含了 inner 函数。如果你调用 ret(),它将输出 10,因为 inner 函数引用了外部的 a 变量,而 a 的值在 func 函数被调用时被设置为 10。
再看下面一段代码
def func():
a = 0
def inner():
nonlocal a
a += 1
return a
return inner
ret = func()
a = 20 #此时即使出现了全局变量a=20,也不会干扰到func函数局部变量a的计数器累计
# inner => ret => 什么时候执行
r1 = ret()
print(r1) #第一次输出,结果为1
# 可能1000000行后才执行
r2 = ret()
print(r2) #第二次输出,结果为2
print(ret()) #结果为3
print(ret()) #结果为4
这段代码,常驻于内存,有实现内部计数器的作用。
装饰器
装饰器本质上是一个闭包
作用:在不改变原有函数调用的情况下. 给函数增加新的功能.(直白地说: 可以在函数前后添加新功能, 但是不改原来的代码)
哪里会用到装饰器:
- 程序用户登录的地方
- 操作日志(增、删、改、查)
- 可以在函数前后添加新功能, 但是不改变原来的代码功能。
一、推导装饰器需要用到的原理:
- 函数可以做为参数进行传递(代理执行)
def func():
print('我是函数') def gggg(fn): # fn要求是个函数
fn() # func() gggg(func) - 函数可以作为返回值进行返回(闭包)
def func():
def inner():
print("123")
return inner ret = func()
ret() - 函数名称可以当成变量一样进行赋值操作
def func1():
print("我是函数1")
def func2():
print("我是函数2") func1 = func2
func1()
二、装饰器的推导过程:
1.定义2个游戏,运行2个游戏
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
play_dnf()
play_lol()
2.此时我们出现了新的需求:
- 在运行dnf游戏前,先打开外挂;在结束游戏后,关闭外挂。
- 在运行lol游戏前,先打开外挂;在结束游戏后,关闭外挂。
- 我们要用游戏管家自动搞这2个事情
因此我们又试图定义了一个管家,并且用代理执行的逻辑(函数可以做为参数进行传递)传输函数执行.
def guanjia(game):
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
guanjia(paly_dnf)
guanjia(paly_lol)
但是我们运行后发现:打游戏的主题不是我,而是管家 。这样就非常的不合理。因为运行的主体变了。我们只想要管家负责在我玩游戏之前开外挂、我玩游戏之后关外挂,而不是代理打游戏。
因此我们使用了:
1、闭包的玩法(函数可以作为返回值进行返回);
2、函数名称可以当成变量一样进行赋值操作,再次对游戏进行封装;
def guanjia(game):
def inner():
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
def play_dnf():
print("开始玩dnf游戏")
def play_lol():
print("开始玩lol游戏")
play_dnf = guanjia(play_dnf) # 让管家把游戏重新封装一遍. 我这边把原来的游戏替换了
play_lol = guanjia(play_lol) # 让管家把lol也重新封装一下.
play_dnf() # 此时运行的是管家给的内层函数inner
play_lol()
解读:这里是把play_dnf(实参)传给game(形参),然后用return返回inner,把打开外挂、玩游戏、关闭外挂一切都返回给(全新的)打包的play_dnf = guanjia(play_dnf),让一切都是我执行的。
这样操作就把原先的管家打游戏,变回了我打游戏,只是这一切都是“我自己操作的”。主体没有发生变化。
*注意:因为这种写法play_dnf = guanjia(play_dnf)非常不容易阅读,容易造成混淆,并且有更好的替代写法——在运行程序的前面进行标记(@guanjia),每次你要运行程序之前,guanjia就会自动加载封装的代码。
def guanjia(game):
def inner():
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf():
print("开始玩dnf游戏")
@guanjia # 相当于 play_dnf = guanjia(play_lol)
def play_lol():
print("开始玩lol游戏")
play_dnf() # 因为前面有@guanjia,这里就相当于 play_dnf = guanjia(play_dnf)
play_lol() # 因为前面有@guanjia,这里就相当于 play_dnf = guanjia(play_lol)
至此,我们搞到了装饰器的雏形:
def wrapper(fn): #wrapper: 装饰器, fn: 目标函数
def inner:
pass # 在目标函数执行之前操作
fn() # 执行目标函数
pass # 在目标函数执行之后操作
return inner #千万别加()
@wrapper #让装饰器代理运行目标函数
def target(): #目标函数
pass
target() # 实际上这里运行的是wrapper里的 =>inner()
被装饰函数的参数问题
新的问题又来了:比如目标函数中有参数,打lol游戏或者dnf要输入账号和密码
def play_dnf(username, password):
print("打开dnf,输入账号密码。 ", username, password)
print('开始玩dnf游戏!')
play_dnf(admin,123456)
#运行结果
打开dnf,输入账号密码。 admin 123456
开始玩dnf游戏!
不加装饰器,我们这样是可以正常执行程序的。
但是加这个装饰器雏形里是没有办法加入任何参数的
def guanjia(game):
def inner():
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。 ", username, password)
print('开始玩dnf游戏!')
play_dnf("admin","123456")
#执行结果
play_dnf("admin", "123456")
TypeError: guanjia.<locals>.inner() takes 0 positional arguments but 2 were given #在guanjia的局部变量的inner里,没有位置接收变量,但是程序却给了2个
Process finished with exit code 1
TypeError: guanjia.<locals>.inner() takes 0 positional arguments but 2 were given 在guanjia的局部变量的inner里,没有位置接收变量,但是程序却给了2个
因此我们需要改造装饰器的def inner()为def inner(username,password)如下执行
def guanjia(game):
def inner(username, password):
print("打开外挂")
game() # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。 ", username, password)
print('开始玩dnf游戏!')
play_dnf("admin", "123456")
#执行结果
打开外挂
Traceback (most recent call last):
File "D:\装饰器.py", line 13, in <module>
play_dnf("admin", "123456")
File "D:\装饰器.py", line 4, in inner
game() # 玩起来了
^^^^^^
TypeError: play_dnf() missing 2 required positional arguments: 'username' and 'password'
Process finished with exit code 1
第13行存在的参数,给到第4行的game() 时,缺少了2个参数'username' and 'password'
为此我们也需要将game()变成game(username, password),把参数也给到game中,并将来一起作为inner返回给原函数做调用(来回穿透传输),程序就顺利执行起来了.
def guanjia(game):
def inner(username, password):
print("打开外挂")
game(username, password) # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。 ", username, password)
print('开始玩dnf游戏!')
play_dnf("admin", "123456")
#执行结果
打开外挂
打开dnf,输入账号密码。 admin 123456
开始玩dnf游戏!
关闭外挂
但是,新的问题又来了,dnf暂时解决了,但是再加入lol的时候,又来了新问题,lol的参数更多,还有hero.
def guanjia(game):
def inner(username, password):
print("打开外挂")
game(username, password) # 玩起来了
print('关闭外挂')
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。", username, password)
print("开始玩dnf游戏!")
@guanjia
def play_lol(username, password, hero):
print("打开lol,输入账号密码,选择英雄。", username, password, hero)
print("开始玩lol游戏!")
play_dnf("admin", "123456")
play_lol("admin", "456789", "盖伦")
#执行结果
play_lol("admin", "456789", "盖伦")
TypeError: guanjia.<locals>.inner() takes 2 positional arguments but 3 were given
TypeError: guanjia.<locals>.inner() takes 2 positional arguments but 3 were given在guanjia的局部变量的inner里,有2个位置函数接收变量,但是程序却给出了3个。
这里为了要让guanjia接收到任意函数,并回传给目标函数。我们得采用通用写法,可实现通用调用。
def guanjia(game):
# *和**表示接收所有参数, *把所有位置参数打包成元组;**把所有关键字参数打包成字典。
def inner(*args, **kwargs): # 给inner添加了参数, args一定是一个元组;kwargs一定是字典(admin, 123456, "大盖伦")
print("打开外挂")
# *, ** 表示把args元组打散成位置参数,以及把kwargs字典打散成关键字参数,传递进game()去
game(*args, **kwargs) # 玩起来了 # game('admin', '123456', "大盖伦")
print('关闭外挂')
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。", username, password)
print("开始玩dnf游戏!")
@guanjia
def play_lol(username, password, hero):
print("打开lol,输入账号密码,选择英雄。", username, password, hero)
print("开始玩lol游戏!")
play_dnf("admin", "123456")
play_lol("admin", "456789", "盖伦")
注意:在game(*args, **kwargs)这个表达式中,*args的作用是将元组中的元素解包,分别作为位置参数传递给func函数。这就是所谓的“打散”操作。同样地,**kwargs会将字典中的键值对解包成关键字参数传递给game。
可以参考阅读:实参位置调用列表和字典——最后的两个案例。
被装饰函数的返回值问题:
说完了参数问题,还要考虑返回值问题,比如玩dnf掉落:屠戮之刃:return"掉落:屠戮之刃",在没有guanjia处理的时候应该这样操作函数,接收返回值:
def play_dnf(username, password):
print("打开dnf,输入账号密码。", username, password)
print("开始玩dnf游戏!")
return"掉落:屠戮之刃"
ret=play_dnf("admin", "123465")
print(ret)
这里我们可以分析到,return是需要在guanjia的game()中运行后获得的返回值,为此我们应该给game()做一个ret接收,并且在inner里return ret
def guanjia(game):
def inner(*args, **kwargs):
print("打开外挂")
game(*args, **kwargs) # =>应该在这里接收返回值
print('关闭外挂')
return inner
因此,整个代码应该
def guanjia(game):
def inner(*args, **kwargs):
print("打开外挂")
ret = game(*args, **kwargs) # 这里是目标函数的执行, 这里是能够从目标函数拿到返回值的.
print('关闭外挂')
return ret #在这里将返回值返回全局(局部变量)
return inner
@guanjia
def play_dnf(username, password):
print("打开dnf,输入账号密码。", username, password)
print("开始玩dnf游戏!")
return"掉落:屠戮之刃"
ret = play_dnf("admin", "123465") #这里虽然也叫ret,但是是全局变量,不要混淆
print(ret)
至此,整个装饰器的推导过程完成了。
通用装饰器的写法:
def wrapper(fn): wrapper: 装饰器, fn: 目标函数
def inner(*args, **kwargs):
pass # 在目标函数执行之前.....
ret = fn(*args, **kwargs) # 执行目标函数
pass # 在目标函数执行之后.....
return ret
return inner # 千万别加()
@wrapper
def target():
pass
ret = target() # 在外层,执行内层函数 =>inner()
一个函数可以被多个装饰器装饰:
@wrapper1
@wrapper2
def target():
print('我是目标')
规则和规律:wrapper1进入——wrapper2进入——target()——wrapper2出去——wrapper1出去
def wrapper1(fn): # fn: wrapper2.inner
def inner(*args, **kwargs): # 1
print("这里是wrapper1 进入") # 2
ret = fn(*args, **kwargs) # 3 => wrapper2.inner
print("这里是wrapper1 出去") # 11
return ret # 12
return inner # 13
def wrapper2(fn): # fn: target
def inner(*args, **kwargs): # 4
print("这里是wrapper2 进入") # 5
ret = fn(*args, **kwargs) # 6 => taget
print("这里是wrapper2 出去") # 8
return ret # 9
return inner # 10
@wrapper1 # target = wrapper1(wrapper2.inner) => target: wrapp1.inner
@wrapper2 # target = wrapper2(target) => target: wrapper2.inner
def target():
print('目标函数') # 7
target() # 0 => 从这里开始
#运行结果
这里是wrapper1 进入 #套壳顺序4 #输出顺序1
这里是wrapper2 进入 #套壳顺序2 #输出顺序2
目标函数 #套壳顺序1 #输出顺序3
这里是wrapper2 出去 #套壳顺序3 #输出顺序4
这里是wrapper1 出去 #套壳顺序5 #输出顺序5
这里可以看到,目标函数在结构上的规律是:
- 最中间是目标函数
- 先被离着最近的
@wrapper2上下套壳 - 再被上层的
@wrapper1上下套壳 - 套壳顺序和输出顺序是不一样的
*大家自行掌握这个规律即可,不用关心我做的序号。这里只是为了方便展示,我自己做了个顺序标记。
装饰器也可以传参
装饰器的实战操作
登录状态判断:login_flag
账号密码判断:while 1 、break
login_flag = False
def login_verify(fn):
def inner(*args, **kwargs):
global login_flag
if login_flag == False: # 关键判断位置*****
# 这里完成登录校验
print('还未完成用户登录操作')
while 1: # 无脑循环登录
username = input("输入你的账号")
password = input("输入你的密码")
if username == "admin" and password == "123":
print("登录成功")
login_flag = True
break #成功后退出循环
else:
print("登录失败, 用户名或密码错误")
ret = fn(*args, **kwargs) # 后续程序的执行
return ret
return inner
@login_verify
def add():
print("添加员工信息")
@login_verify
def delete():
print("删除信息")
@login_verify
def upd():
print("修改信息")
@login_verify
def search():
print("查询员工信息")
add()
upd()
delete()
search()
Python——第四章:闭包(Closure)、装饰器(Decorators)的更多相关文章
- Python之面向对象:闭包和装饰器
一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...
- Python函数进阶:闭包、装饰器、生成器、协程
返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...
- python 内嵌函数, 闭包, 函数装饰器
一. 函数内嵌 闭包 在python中,函数可以作为返回值, 可以给变量赋值. 在python中, 内置函数必须被显示的调用, 否则不会执行. #!/usr/bin/env python #-*- ...
- Python之命名空间、闭包、装饰器
一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...
- Python 变量作用域,闭包和装饰器
from dis import dis b = 6 def f1(a): print(a)print(b) b = 9 f1(3) print(dis(f1)) # dis模块可以查看python函数 ...
- python函数作用域,闭包,装饰器
第一:函数作用域: L:local 函数内部作用域 E:enclosing 函数内部与内嵌函数之间(闭包) G:global 全局作用域 B:build_in ...
- python函数知识七 闭包、装饰器一(入门)、装饰器二(进阶)
21.闭包 闭包:在嵌套函数内,使用非全局变量(且不使用本层变量) 闭包的作用:1.保证数据的安全性(纯洁度).2.装饰器使用 .__closure__判断是否是闭包 def func(): a = ...
- 简学Python第四章__装饰器、迭代器、列表生成式
Python第四章__装饰器.迭代器 欢迎加入Linux_Python学习群 群号:478616847 目录: 列表生成式 生成器 迭代器 单层装饰器(无参) 多层装饰器(有参) 冒泡算法 代码开发 ...
- 13、python中的函数(闭包与装饰器)
一.嵌套函数 函数的内部又再定义另一个函数,这个函数就叫嵌套函数,里面含函数就叫内部函数. 示例: 二.返回函数 函数可以接收函数对象作为参数,同理函数也能返回一个函数对象作为返回值. 示例: 返回函 ...
- python闭包与装饰器
转自小马哥: 闭包和装饰器充分体现了Python语法糖的优雅感觉. 在本文中,我们的实验要完成两个工作,一个是加法,一个是累计调用加法的次数,最普通的Python程序可以这么写: def valida ...
随机推荐
- 设备维修保养通知:如何使用API接口发送通知给相关人员
在设备维修保养管理中,及时通知相关人员是确保设备得到及时维护的关键.API接口提供了一个方便的方式来自动发送维修保养通知,以确保工作流程的顺利进行.本文将详细介绍如何使用成熟的API接口来发送设备维修 ...
- (数据科学学习手札154)geopandas 0.14版本新特性一览
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,就在前两天,Python生态中 ...
- Solution -「洛谷 P5072」「YunoOI 2015」盼君勿忘
Description Link. 无修支持查询:查询一个区间 \([l,r]\) 中所有子序列分别去重后的和 \(\bmod\ p\) Solution 这是数据结构一百题的第50题(一半了哦)的纪 ...
- Towards Network Anomaly Detection Using Graph Embedding笔记
Towards Network Anomaly Detection Using Graph Embedding 目录 Towards Network Anomaly Detection Using G ...
- SSM-Mybatis笔记
目录 Mybatis-9.28 1.简介 1.1.什么是Mybatis 1.2.持久化 1.3.持久层 1.4 为什么需要Mybatis? 2.第一个Mybatis程序 2.1.搭建环境 2.2.创建 ...
- QFluentWidgets: 基于 C++ Qt 的 Fluent Design 组件库
简介 QFluentWidgets 是一个基于 Qt 的 Fluent Designer 组件库,内置超过 150 个开箱即用的 Fluent Designer 组件,支持亮暗主题无缝切换和自定义主题 ...
- 其它——CGI,FastCGI,WSGI,uWSGI,uwsgi一文搞懂
文章目录 CGI, FastCGI, WSGI, uWSGI, uwsgi一文搞懂 一 CGI 二 FastCGI 三 WSGI 四 uWSGI 五 uwsgi CGI, FastCGI, WSGI, ...
- 其它——MySQL主从搭建基于docker
文章目录 10分钟搭建MySQL主从同步(基于docker) 一 主从配置原理 二 操作步骤 2.1我们准备两台装好mysql的服务器(我在此用docker模拟了两台机器) 2.2 远程连接入主库和从 ...
- 轻松掌握组件启动之Redis单机、主从、哨兵、集群配置
单机配置启动 Redis安装 下载地址:http://redis.io/download 安装步骤: 1: 安装gcc编译器:yum install gcc 2: 将下载好的redis‐5.0.3.t ...
- git 创建本地分支并关联远程分支
1.查看远程分支 git branch 可以看到,我本地只有dev和master分支.现在同事创建了一个远程分支dev-glq,里面是他的代码.我应该再我本地创建一个分支,并且他的关联远程分支. 2. ...