
⼀ 、闭包
1、作⽤域
在Python代码中,作⽤域分为两种情况:全局作⽤域 与 局部作⽤域
2、变量的作⽤域
在全局定义的变量 => 全局变量 在局部定义的变量 => 局部变量
3、全局变量与局部变量的访问范围
① 在全局作⽤域中可以访问全局变量,在局部作⽤域中可以访问局部变量
② 在局部作⽤域中可以访问全局变量
③ 在全局作⽤域中不能访问局部变量
4、问题:为什么在全局作⽤域中⽆法访问局部变量
主要原因在于,在Python的底层存在⼀个“ 垃圾回收机制” ,主要的作⽤就是回收内存空间。加快计 算机的运⾏。我们在Python代码中定义的变量也是需要占⽤内存的,所以Python为了回收已经被已经过 的内存,会⾃动将函数运⾏以后的内部变量和程序直接回收。
5、问题:我们有没有办法把函数内部的局部变量保留
闭包 在函数嵌套的前提下,内部函数使⽤了外部函数的变量,并且外部函数返回了内部函数,我们把这个使 ⽤外部函数变量的内部函数称为闭包。
前提:函数有嵌套
条件:
1、内部函数使用了外部函数的变量
2、外部函数返回了内部函数
闭包的目的:是为了保留函数内部的变量,不让它随着函数的运行结束而消失.
# 举例
def outer_fun():
# 定义一个外部函数的局部变量
score = 100
def inner_fun():# 把inner_fun()称之为闭包
# 条件1:在内部函数中使用外部函数定义的局部变量
avg = score *10/2
print(f"平均成绩是:{avg}")
# 条件2:返回内部函数(在外部函数的函数体里)
return inner_fun
6、闭包的构成条件(三步⾛)
第⼀步:有嵌套
第⼆步:有引⽤
第三步:有返回(return)
def func():
num = 20 # 局部变量
def inner():
print(num)
return inner # 实际inner函数并没有执⾏,只是返回了inner函数在内存中的地址
f = func() # 相当于把inner在内存中的地址0x7fbc9b3f8e18赋值给变量f
print(id(f)) # 0x7fbc9b3f8e18
f() # 找到inner函数的内存地址,并执⾏器内部的代码(num=20),在于闭包函数保留了num=20这个局部变

闭包的作⽤:正常情况下,当执⾏func()的时候,函数内部的变量num = 20,会随着函数的func函数的 结束⽽被垃圾回收机制所回收。
所以闭包的真正作⽤:就是可以在全局作⽤域中,实现间接对局部变量 进⾏访问。
7、注意事项
注意点: 由于闭包引⽤了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
8、在闭包的内部实现对外部变量的修改
示例一:直接修改的话是无法成功的
def outer():
count = 0 # outer函数的局部变量
def inner():
count = 10 # 直接修改outer函数中的count变量
print('outer函数中的num:', count) # 0
inner() #执⾏函数inner,让num=10⽣效
print('outer函数中的num:', count) # 0
return inner
counter = outer()
print(counter()) #输出None

示例二:使⽤ nonlocal关键字(在函数内部修改函数外部的变量,这个变量⾮全局变量)
def outer():
count = 0 # outer函数的局部变量
def inner():
nonlocal count #声明count不是局部变量,也不是全局变量,⽽是外部函数outer的局部变量
count = 10 # 直接修改outer函数中的count变量
print('outer函数中的num:', count) # 0
inner() #执⾏函数inner,让num=10⽣效
print('outer函数中的num:', count) # 0
return inner
counter = outer()
print(counter()) #输出None

在 inner 函数中使⽤ nonlocal 关键字,会修改 outer 函数作⽤域内的 count 变量。因此,第⼆次 打印 outer 函数中的 count 时,它将输出10。
示例三:使⽤ global关键字(在函数内部声明变量,代表引⽤全局作⽤域中的全局变量)
def outer():
count = 0 # outer函数的局部变量
def inner():
global count
# nonlocal count #声明count不是局部变量,也不是全局变量,⽽是外部函数outer的局部变量
count = 10 # 直接修改outer函数中的count变量
print('outer函数中的num:', count) # 0
inner() #执⾏函数inner,让num=10⽣效
print('outer函数中的num:', count) # 0
return inner
counter = outer()
print(counter()) #输出None

global 关键字是⽤来在函数内部声明⼀个变量是全局变量,⽽不是局部变量。
当你在 inner 函数中 使⽤ global count 时,你实际上是在声明⼀个全局变量 count ,并将其设置为10。
然⽽,这并不会 影响到 outer 函数中的局部变量 count 。
总结:
global使用场景:
当在局部作用域中 修改 全局变量时,通过global关键字来说明
nonlocal使用场景:
在函数嵌套的场景下,想在 内部函数的作用域中修改外部函数作用域中的变量时
闭包的作用:在全局作用域中间接使用了局部变量(因为它能保留函数内部的变量)
在学习闭包时,有可能会出现内部函数的局部变量修改外部函数的局部变量的情况
因此使用nonlocal能解决该问题
二、装饰器
1、什么是装饰器
在不改变现有函数源代码以及函数调⽤⽅式的前提下,实现给函数增加额外的功能。
装饰器的本质就是⼀个闭包函数(三步:① 有嵌套 ② 有引⽤ ③ 有返回)
有返回代表外部函数返回内部函数的内存地址(内部函数的名称)
2个前提:
不改变函数的源代码
不改变函数的调用方式
对函数的原功能做了加强或者补充
2、装饰器的雏形
装饰器:本质是⼀个闭包,有嵌套、有引⽤、有返回(返回的是函数的内存地址)
# 要求:把登录功能封装起来(⽐如封装成⼀个函数,添加这个登录不能影响现有功能函数)
def check(fn):
def inner():
# 开发登录功能
print('登录功能')
#调⽤原函数
fn() #调⽤comment()
return inner
#评论功能(前提:登录)
def comment():
print('评论功能')
comment = check(comment) # 调⽤check(),把comment函数的内存地址赋值给comment
comment() # 调⽤inner函数
#下载功能(前提:登录)
def download():
print('下载功能')
download = check(download)# 调用check()。把comment函数的内存地址赋值给download
download()# 调用inner函数

参数 fn 在 check 中也是⼀个局部变量
参数 fn :就是要装饰的函数的函数名,如 comment ,如 download
3、装饰器定义
'''
装饰器:本质就是⼀个闭包①有嵌套②有引⽤③有返回
'''
def check(fn):
def inner():
# 开发登录验证功能
print('验证登录')
# 执⾏原有函数
fn()
return inner
@check
def comment():
print('发表评论')
comment()

装饰器代码的执⾏流程:
1. check 是⼀个装饰器函数,它接收⼀个函数 fn 作为参数。
2. 在 check 内部定义了⼀个名为 inner 的嵌套函数,它⾸先执⾏登录验证功能,然后调⽤传⼊的原 始函数 fn 。
3. 使⽤ @check 语法糖将装饰器应⽤于comment 函数。
4. 当调⽤ comment() 时,实际上是调⽤了装饰器 check 内部的inner 函数。
5. inner 函数先打印’ 验证登录’ ,然后调⽤原始的 comment 函数打印’ 发表评论’ 。
4、装饰器的作⽤:获取程序的执⾏时间
'''
定义获取程序的执⾏时间装饰器=> 闭包(①有嵌套②有引⽤③有返回)
'''
import time
def get_time(fn):
def inner():
# ①添加装饰器修饰功能(获取程序的执⾏时间)
begin = time.time()
# ②调⽤fn函数,执⾏原函数代码
fn()
end = time.time()
print(f'这个函数的执⾏时间:{end - begin}')
return inner
@get_time
def demo():
for i in range(1000000):
print(i)
demo()

5、带有参数装饰器
'''
带有参数的装饰器:①有嵌套②有引⽤③有返回
'''
def logging(fn):
def inner(*args, **kwargs):
# 添加装饰器代码(输出⽇志信息)
print('--⽇志信息:正在努⼒计算机- -')
# 执⾏要修饰的函数
fn(*args, **kwargs) # sum_num(a, b)
return inner
@logging
def sum_num(*args, **kwargs):
result = 0
# *args代表不定⻓元组参数,args = (10, 20)
for i in args: # i = 10, i = 20
result += i
# **kwargs代表不定⻓字典参数, kwargs = {a:30, b: 40}
for i in kwargs.values(): # i = 30, i = 40
result += i
print(result)
# sum_num带4个参数,⽽且类型不同,10和20以元组形式传递,a = 30,b = 40以字典形式传递
sum_num(10, 20, a=30, b=40)

6、带有返回装饰器
'''
带有返回值的装饰器:①有嵌套②有引⽤③有返回如果⼀个函数执⾏完毕后,没有return返回值,则默认返回None
'''
def logging(fn):
def inner(*args, **kwargs):
print('--⽇志信息:正在努⼒计算 - -')
return fn(*args, **kwargs) # fn() = sub_num(20, 10) = result
return inner
@logging
def sub_num(a, b):
result = a - b
return result
print(sub_num(20, 10))

7、通⽤版本的装饰器(以后所有的装饰器以此为准)
所谓的通用装饰器
闭包的基础上,
加参数:1、外部函数加上一个 函数 参数;2、内部函数加上不定长参数加返回值
'''
通⽤装饰器:①有嵌套②有引⽤③有返回④有不定⻓参数⑤有return返回值
'''
def logging(fn):
def inner(*args, **kwargs):
# 输出装饰器功能
print('--正在努⼒计算 - -')
# 调⽤fn函数
return fn(*args, **kwargs)
return inner
@logging
def sum_num1(a, b):
result = a + b
return result
print(sum_num1(20, 10))
@logging
def sum_num2(a, b, c):
result = a + b + c
return result
print(sum_num2(10, 20, 30))

8、装饰器⾼级:使⽤装饰器传递参数(了解)
基本语法:
def 装饰器(fn):
...
@装饰器('参数')
def 函数():
#函数代码
示例:
'''
通⽤装饰器:①有嵌套②有引⽤③有返回④有不定⻓参数⑤有return返回值
真正问题:通过装饰器传递参数,我们应该如何接收这个参数呢?
答:在logging⽅法的外侧在添加⼀个函数,专⻔⽤于接收传递过来的参数
'''
def logging(flag):
# flag = + 或flag = -
def decorator(fn):
def inner(*args, **kwargs):
if flag == '+':
print('--⽇志信息:正在努⼒进⾏加法运算- -')
elif flag == '-':
print('--⽇志信息:正在努⼒进⾏减法运算 - -')
return fn(*args, **kwargs)
return inner
return decorator
@logging('+')
def sum_num(a, b):
result = a + b
return result
@logging(' - ')
def sub_num(a, b):
result = a - b
return result
print(sum_num(10, 20))
print(sub_num(100, 80))

执⾏过程和解释:
1. 定义 logging 函数:
○ logging 函数是⼀个装饰器⼯⼚,它接收⼀个参数 flag ,这个参数⽤于确定要执⾏的运算 类型(加法或减法)。
○ logging 函数内部定义了⼀个装饰器
2. 定义 decorator 装饰器:
○ decorator ,它接收⼀个函数 fn 作为参数。 decorator 是⼀个真正的装饰器,它接收⼀个函数 fn ,并返回⼀个内部函数 inner 。
○ inner 函数接收不定⻓参数 *args 和关键字参数 传递给原始函数 fn 。 **kwargs ,这使得它可以接收任何参数并 14
3. 在 inner 函数内部:
○ 根据 flag 参数的值,打印出相应的⽇志信息。
○调⽤原始函数 fn ,并传递inner 接收到的所有参数。
○返回原始函数的返回值。
4. 使⽤ @logging('+') 和 @logging('-') 装饰器:
○ 使⽤ @logging('+') 装饰 sum_num 函数,将 递 '+' 作为参数。
○使⽤ @logging('-') 装饰 logging 装饰器应⽤于该函数,并传 sub_num 函数,将 logging 装饰器应⽤于该函数,并传递 ' 作为参数。
5. 定义 sum_num 和 sub_num 函数:
○ sum_num 函数接收两个参数,计算它们的和,并返回结果。
○ sub_num 函数接收两个参数,计算它们的差,并返回结果。
6. 调⽤ sum_num 和 sub_num 函数:
○ 调⽤ sum_num(10, 20) ,装饰器会打印加法⽇志信息,并返回30。
○调⽤ sub_num(100, 80) ,装饰器会打印减法⽇志信息,并返回20。
9、应用举例
现在项目小组的组长,给了你一个任务,让你设计一个通用性的装饰器供整个开发小组的人使用。
问题:
小组一共有3个人,
小张负责写登录功能函数
小李负责写评论功能函数
小刘负责写喜欢功能函数
假设,小组3人都需要统计自己所写函数运行消耗的时间除此以外,
小张还需要在执行登录功能之前,记录登录者的ip和时间
小李还需要在评论功能之前,要验证是否登录
小刘还需要在喜欢功能之前,验证是否已关注
分析:
1、先定义出张、李、刘3人原始的函数
2、定义一个装饰器,去统计函数花费的时间(大家都共用的装饰器)
3、再定义一个装饰器,去满足个人不同业务场景的需求(个人特色)
思路:
1、先写的3人的原始函数
2、调用3人的原始函数(此时没有任何增强的效果)
3、写3人都具备的通用增强功能,即对原始函数执行所消耗的时间。时间消耗统计的装饰器
4、不同人员各自的特色增强函数,通过传递一个函数作为装饰器内部函数的参数来进行关联
5、编写各自特色的增强函数
6、调用时增加特色函数名作为参数传递给装饰器
"""
装饰器的本质是闭包,它的作用是为了给 原始函数 进行功能增强
"""
def outer_fun(fun):
def inner_fun(your_doing_fun,*args,**kwargs):
# print("写装饰代码-----在原始函数运行之前")
# 运行调用者想要执行的个性行为函数.
# 张 记录登录者的ip + 登录时间
# 李 验证登录
# 刘 检验是否已经关注
your_doing_fun()
before = time.time()# 记录原始函数运行前的时间
run_res = fun(*args,**kwargs) # 运行原始函数
after = time.time()#记录原始函数运行之后的时间
print(f"函数运行消耗了:{after - before}秒")
# print("写装饰代码-----在原始函数运行之后")
return run_res
return inner_fun
@outer_fun
def zhang():
print("张 的原始 登录函数 已执行.........")
@outer_fun
def li():
print("李 的原始 评论函数 已执行。。。。。。")
@outer_fun
def liu():
print("刘 的原始 喜欢函数 已执行----------")
# 定义自己传入进去的特色功能函数
def zhang_self():
print("张自己的个人其它增强功能-------记录登录者的ip + 登录时间")
def li_self():
print("李 验证登录----------------")
def liu_self():
print("刘 检验是否已经关注==========")
zhang(zhang_self)
li(li_self)
liu(liu_self)


2011

被折叠的 条评论
为什么被折叠?



