闭包:

本质, 内层函数对外层函数的局部变量的使用. 此时内层函数被称为闭包函数
    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()调用retinner函数),程序为了能够持续提供可用性,会将该段代码常驻于内存。不会被内存回收。

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

这段代码,常驻于内存,有实现内部计数器的作用。

装饰器

装饰器本质上是一个闭包

作用:在不改变原有函数调用的情况下. 给函数增加新的功能.(直白地说: 可以在函数前后添加新功能, 但是不改原来的代码)

哪里会用到装饰器:

  1. 程序用户登录的地方
  2. 操作日志(增、删、改、查)
  3. 可以在函数前后添加新功能, 但是不改变原来的代码功能。

一、推导装饰器需要用到的原理:

  1. 函数可以做为参数进行传递(代理执行)

    def func():
    print('我是函数') def gggg(fn): # fn要求是个函数
    fn() # func() gggg(func)
  2. 函数可以作为返回值进行返回(闭包)
    def func():
    def inner():
    print("123")
    return inner ret = func()
    ret()
  3. 函数名称可以当成变量一样进行赋值操作
    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是需要在guanjiagame()中运行后获得的返回值,为此我们应该给game()做一个ret接收,并且在innerreturn 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

这里可以看到,目标函数在结构上的规律是:

  1. 最中间是目标函数
  2. 先被离着最近的@wrapper2上下套壳
  3. 再被上层的@wrapper1上下套壳
  4. 套壳顺序和输出顺序是不一样的

*大家自行掌握这个规律即可,不用关心我做的序号。这里只是为了方便展示,我自己做了个顺序标记。

装饰器也可以传参

装饰器的实战操作

登录状态判断:login_flag

账号密码判断:while 1break

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)的更多相关文章

  1. Python之面向对象:闭包和装饰器

    一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...

  2. Python函数进阶:闭包、装饰器、生成器、协程

    返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...

  3. python 内嵌函数, 闭包, 函数装饰器

    一.  函数内嵌 闭包 在python中,函数可以作为返回值, 可以给变量赋值. 在python中, 内置函数必须被显示的调用, 否则不会执行. #!/usr/bin/env python #-*- ...

  4. Python之命名空间、闭包、装饰器

    一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...

  5. Python 变量作用域,闭包和装饰器

    from dis import dis b = 6 def f1(a): print(a)print(b) b = 9 f1(3) print(dis(f1)) # dis模块可以查看python函数 ...

  6. python函数作用域,闭包,装饰器

    第一:函数作用域: L:local 函数内部作用域 E:enclosing       函数内部与内嵌函数之间(闭包) G:global            全局作用域 B:build_in    ...

  7. python函数知识七 闭包、装饰器一(入门)、装饰器二(进阶)

    21.闭包 闭包:在嵌套函数内,使用非全局变量(且不使用本层变量) 闭包的作用:1.保证数据的安全性(纯洁度).2.装饰器使用 .__closure__判断是否是闭包 def func(): a = ...

  8. 简学Python第四章__装饰器、迭代器、列表生成式

    Python第四章__装饰器.迭代器 欢迎加入Linux_Python学习群  群号:478616847 目录: 列表生成式 生成器 迭代器 单层装饰器(无参) 多层装饰器(有参) 冒泡算法 代码开发 ...

  9. 13、python中的函数(闭包与装饰器)

    一.嵌套函数 函数的内部又再定义另一个函数,这个函数就叫嵌套函数,里面含函数就叫内部函数. 示例: 二.返回函数 函数可以接收函数对象作为参数,同理函数也能返回一个函数对象作为返回值. 示例: 返回函 ...

  10. python闭包与装饰器

    转自小马哥: 闭包和装饰器充分体现了Python语法糖的优雅感觉. 在本文中,我们的实验要完成两个工作,一个是加法,一个是累计调用加法的次数,最普通的Python程序可以这么写: def valida ...

随机推荐

  1. 6. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇

    用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy githu ...

  2. Linux系列教程——Linux发展介绍、Linux系统安装、查看Linux内核版本和系统版本、Centos7安装jdk1.8

    文章目录 1 Linux发展介绍 零 什么是Linux 一 Linux前身 二 Linux诞生 三 开源文化 四 Linux系统特点 五 Linux分支 2 Linux系统安装 Linux虚拟机安装 ...

  3. [GXYCTF 2019]Ping Ping Ping

    题目就是ping,而且这还有一个查询窗口,就随便查询试试 ping了一下本地,发现没有什么很大的作用,给出了提示是php可以执行系统函数这就感到神奇了,就还是上网搜了搜 发现可以在查询IP后面进行拼接 ...

  4. [AHOI2002] Kitty猫基因突变

    我们不妨将所有权值打到一棵树上,这很容易想到. 考虑暴力,如果我们选择了 \(w\) 个点,修改后我们会从叶子节点依次合并去计算贡献. 很显然我们可以动态规划维护. \(f[p][w][0/1/2]\ ...

  5. YXの每日挂分记录

    7.11 T1 不开两倍数组 100->60. 7.18 T2 dp+矩乘 转移不判边界 100->10. 7.20 T2 人类智慧 1e6 n log n 100->10,求前 5 ...

  6. 数据结构与算法 | 二分搜索(Binary Search)

    二分搜索(Binary Search) 文承上篇,搜索算法中除了深度优先搜索(DFS)和广度优先搜索(BFS),二分搜索(Binary Search)也是最基础搜索算法之一. 二分搜索也被称为折半搜索 ...

  7. 电路中的N.M.缩写含义

    国外的一些电路中会发现在一些器件旁会有 N.M. 的标注. N.M. = Not Mount

  8. Educational Codeforces Round 125 (Rated for Div. 2) E. Star MST

    折磨了我三天的\(DP\),终于看懂啦. 首先,如果想要有题目要求的效果,那么最短的边一定都是与\(1\)相连的,就是一个菊花图,生成树里的边就是最短的边. \(f[i][j]\)表示已经有\(i\) ...

  9. (Good topic)卡牌分组(3.27leetcode每日打卡)

    给定一副牌,每张牌上都写着一个整数. 此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:  每组都有 X 张牌. 组内所有的牌上都写着相同的整数. 仅当你可选的 X &g ...

  10. BIRCH算法全解析:从原理到实战

    本文全面解析了BIRCH(平衡迭代削减聚类层次)算法,一种用于大规模数据聚类的高效工具.文章从基础概念到技术细节,再到实战应用与最佳实践,提供了一系列具体的指导和例子.无论你是数据科学新手,还是有经验 ...