前言:之前讲完了面向对象的三大特征,这篇讲解的是闭包与装饰器(作用域,nonlocal关键字,global关键字)
闭包
定义
闭包是指有权访问另一个函数作用域中变量的函数。简单来说,即使该函数已经执行完毕,其作用域内的变量也不会被销毁,而是会被闭包所引用,从而可以在函数外部继续访问这些变量。
形成条件
要形成一个闭包,通常需要满足以下几个条件:
- 嵌套函数:在一个函数内部定义另一个函数。
- 内部函数引用外部函数的变量:内部函数使用了外部函数作用域中的变量。
- 外部函数返回内部函数:外部函数将内部函数作为返回值返回。
作用
- 读取函数内部的变量:闭包可以让我们在函数外部访问函数内部的变量。
- 让这些变量的值始终保持在内存中:由于闭包引用了外部函数的变量,这些变量不会随着外部函数的执行结束而被销毁。
示例代码:
'''
闭包程序三步⾛:① 有嵌套 ② 有引⽤ ③ 有返回
'''
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这个局部变量
作用域
在全局定义的变量 => 全局变量(全局作用域)在局部定义的变量 => 局部变量(局部作用域)
全局变量与局部变量的访问范围
1.在全局作用域中访问全局变量,在局部作用域中访问局部变量
# 全局作⽤域(全局变量)
num1 = 10
def func():
# 局部作⽤域(局部变量)
num2 = 20
# ① 在局部访问局部变量
print(num2)
#在全局访问全局变量
print(num1)
# 调⽤函数
func()
2.在局部作用域中可以访问全局变量
# 全局作⽤域(全局变量)
num1 = 10
def func():
# 局部作⽤域(局部变量)
# ② 在局部作⽤域中可以访问全局变量
print(num1)
# 调⽤函数
func()
3.在全局作用域中不能访问局部变量
# 全局作⽤域(全局变量)
num1 = 10
def func():
# 局部作⽤域(局部变量)
num2 = 20
# 调⽤函数
func()
# 在全局作⽤域中调⽤局部变量num2
print(num2)
主要原因在于,在Python的底层存在一个“垃圾回收机制”,主要的作用就是回收内存空间。 加快计算机的运行。我们在Python代码中定义的变量也是需要占用内存的,所以Python为了回收已经被已经过的内存,会自动将函数运行以后的内部变量和程序直接回收。所以当我们需要函数内部的局部变量保留就需要用到闭包,在函数嵌套的前提下, 内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
nonlocal关键字
基本概念
nonlocal
关键字用于声明一个变量不是当前函数的局部变量,而是外层(非全局)函数的局部变量。当在嵌套函数中需要修改外层函数的局部变量时,就可以使用 nonlocal
关键字。
使用场景
在嵌套函数中,如果内层函数想要修改外层函数的局部变量,直接赋值会创建一个新的局部变量,而不是修改外层函数的变量。此时使用 nonlocal
关键字就能解决这个问题。
示例代码
def outer():
x = 10
def inner():
# 使用 nonlocal 关键字声明 x 是外层函数的局部变量
nonlocal x
x = 20
print(f"内层函数中的 x: {x}")
inner()
print(f"外层函数中的 x: {x}")
outer()
注意事项
- 作用范围:
nonlocal
只能用于嵌套函数中,并且只能引用外层(非全局)函数的局部变量,不能引用全局变量。 - 变量必须已存在:使用
nonlocal
声明的变量必须在外部函数中已经定义,否则会引发SyntaxError
。
global关键字
基本概念
在 Python 中,变量的作用域分为全局作用域和局部作用域。全局变量定义在函数外部,可在整个程序范围内访问;局部变量定义在函数内部,只能在该函数内部访问。global
关键字能让我们在函数内部声明一个变量为全局变量,从而可以对其进行修改。
使用场景
当需要在函数内部修改全局变量的值时,就需要使用 global
关键字。若不使用 global
关键字,在函数内部对同名变量赋值会创建一个新的局部变量,而不是修改全局变量。
示例代码
x = 10
def modify_variable():
# 使用 global 关键字声明 x 是全局变量
global x
x = 20
print(f"函数内部修改后的 x: {x}")
modify_variable()
print(f"全局变量 x: {x}")
注意事项
在函数内部使用 global
关键字声明变量时,必须在使用该变量之前进行声明,否则会引发 SyntaxError
。
global关键字 与 nonlocal关键字对比
global
关键字:用于在函数内部访问和修改全局变量,全局变量定义在函数外部的全局作用域。nonlocal
关键字:用于在嵌套函数中访问和修改外层(非全局)函数的局部变量。
装饰器
基本概念
装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。通过使用装饰器,可以在不改变原函数定义和调用方式的前提下,为原函数添加额外的功能
定义
'''
装饰器:本质就是⼀个闭包 ① 有嵌套 ② 有引⽤ ③ 有返回
'''
def check(fn):
def inner():
# 开发登录验证功能
print('验证登录')
# 执⾏原有函数
fn()
return inner
@check
def comment():
print('发表评论')
comment()
装饰器的作用
🥇获取程序的执行时间
'''
定义获取程序的执⾏时间装饰器 => 闭包(① 有嵌套 ② 有引⽤ ③ 有返回)
'''
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()
🥇带有参数装饰器
# 定义装饰器
def decorator(func):
def inner(a, b):
# 在内部函数对已有函数进行装饰
print("正在努力执行加法计算")
func(a, b)
return inner
# 用装饰器语法糖方式装饰带有参数的函数
@decorator # add_num= decorator(add_num), add_num=inner
def add_num(num1, num2):
result = num1 + num2
print("结果为:", result)
add_num(1, 2)
输出:
--正在努力计算--
3
注意:
- 使用装饰器装饰已有函数的时候,内部函数的类型和要装饰的已有函数的类型(参数、返回值)保持一致。
🥇带有返回装饰器
'''
带有返回值的装饰器:① 有嵌套 ② 有引⽤ ③ 有返回
如果⼀个函数执⾏完毕后,没有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))
因为内部函数的类型和要装饰的已有函数的类型保持一致,当要装饰的已有函数有返回值,内部函数也需要有返回值。
🥇通用版本的装饰器
'''
通⽤装饰器:① 有嵌套 ② 有引⽤ ③ 有返回 ④ 有不定⻓参数 ⑤ 有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))
# 通用装饰器
def decorator(fn):
def inner(*args, **kwargs):
print("--正在努力计算--")
result = fn(*args, **kwargs)
return result
return inner
🥇多个装饰器的使用
def make_div(func):
print("make_div装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = "<div>" + func() + "</div>"
return result
return inner
# 定义装饰器
def make_p(func):
print("make_p装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = "<p>" + func() + "</p>"
return result
return inner
# content = make_div(make_p.inner)
@make_div
@make_p
def content():
return "人生苦短,我用python!"
result = content()
print(result)
🥇装饰器高级:传递参数
基本语法:
def 装饰器(fn):
...
@装饰器('参数')
def 函数():
# 函数代码
示例代码:
def logging(flag):
# flag = + 或 flag = -
def decorator(fn):
def inner(*args, **kwargs):
if flag == '+':
print('-- ⽇志信息:正在努⼒进⾏加法运算 --')
elif flag == '-':
print('-- ⽇志信息:正在努⼒进⾏减法运算 --')
else:
print(f"错误的标志参数 {flag},请传入 '+' 或 '-'。")
return None
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))
🥇类装饰器
基本语法
class 类装饰器():
# 装饰器代码
@类装饰器名称
def 函数():
# 函数代码
示例代码:
'''
类装饰器编写规则:
必须有⼀个__init__初始化⽅法,⽤于接收要装饰函数的函数
必须把这个类转换为可以调⽤的函数
问题:如何把⼀个类当做⼀个装饰器函数进⾏调⽤(把类当做函数)
'''
class Check():
def __init__(self, fn):
# fn就是要修饰函数的名称,当Check装饰器类被调⽤时,系统会⾃动把comment函数名称传递给fn变量
self.__fn = fn
# __call__⽅法:把⼀个类转换为函数的形式进⾏调⽤
def __call__(self, *args, **kwargs):
# 编写装饰器代码
print('请先登录')
# 调⽤comment函数本身
self.__fn(*args, **kwargs)
# 编写⼀个函数,⽤于实现评论功能,底层comment = Check(comment)
@Check
def comment():
print('评论功能')
# 调⽤comment函数,实现评论功能
comment()
输出结果
闭包与装饰器到这里就讲完啦