Python入门13:Python闭包与装饰器
前言:
你是否曾在Python编程中遇到过这样的问题:如何在函数执行后保留局部变量?如何在不修改原函数代码的情况下为其添加新功能?这些问题都可以通过Python中的闭包和装饰器来解决!
本文将带你深入理解Python闭包和装饰器的核心概念,并通过大量代码示例帮助你掌握这些高级编程技巧。无论你是Python初学者,还是有一定经验的开发者,这篇博客都将为你提供宝贵的学习资源。
你将学到:
-
什么是闭包?闭包的构成条件和实际应用。
-
装饰器的定义、作用及如何使用装饰器为函数添加新功能。
-
如何编写通用装饰器,支持参数传递和返回值处理。
-
高级装饰器技巧:类装饰器的使用。
通过本文的学习,你将能够轻松应对Python中的闭包和装饰器问题,提升代码的可维护性和扩展性。快来一起探索Python的高级编程世界吧!
一、闭包
1、为什么要学闭包?
下面将会通过一个例子为你解释学习闭包的重要性:
# 全局作⽤域(全局变量)
num1 = 10
def func():
# 局部作⽤域(局部变量)
num2 = 20
# 调⽤函数
func()
# 在全局作用域中调用全局变量
print(num1)
# 在全局作⽤域中调⽤局部变量num2
print(num2)
运行结果:
通过这个例子,我们发现函数执行结束之后我们并不能够获取函数内部的局部变量。主要原因在于,在Python的底层存在⼀个“垃圾回收机制”,主要的作⽤就是回收内存空间。加快计 算机的运⾏。我们在Python代码中定义的变量也是需要占⽤内存的,所以Python为了回收已经被已经过的内存,会⾃动将函数运⾏以后的内部变量和程序直接回收,这个时候我们就可以通过闭包来解决这种问题了。
2. 什么是闭包?
闭包(Closure)是Python中一个非常重要的概念,尤其是在函数式编程中。简单来说,闭包是指在一个函数内部定义的函数,并且这个内部函数引用了外部函数的变量。闭包的核心作用是保留函数执行时的上下文环境,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的变量。
3. 闭包的构成条件
闭包的构成需要满足以下三个条件:
-
有嵌套:在一个函数内部定义了另一个函数。
-
有引用:内部函数引用了外部函数的变量。
-
有返回:外部函数返回内部函数的引用(即内部函数的内存地址)。
示例代码:
def outer():
num = 20 # 外部函数的局部变量
# 嵌套
def inner():
# 引用
print(num) # 内部函数引用了外部函数的变量
# 返回
return inner # 实际上inner函数并没有执⾏,只是返回了inner函数在内存中的地址
f = outer() # 相当于把inner在内存中的地址赋值给变量f
print(f) #inner函数的内存地址
f() # 找到inner函数的内存地址,并执⾏器内部的代码(num=20),在于闭包函数保留了num=20这个局部变量
运行结果:
在这个例子中,outer
函数内部定义了 inner
函数,并且 inner
函数引用了 outer
函数的局部变量 num
。即使 outer
函数执行完毕,inner
函数仍然可以访问 num
变量。
4. 闭包的作用
闭包的主要作用是保留函数执行时的上下文环境。通常情况下,函数执行完毕后,其内部的局部变量会被垃圾回收机制回收。但通过闭包,我们可以保留这些局部变量,使得在全局作用域中仍然可以间接访问它们。
5、在闭包的内部实现对外部变量的修改
def outer():
count = 0
def inner():
count = 10
print('outer函数中的num:',count)
inner()
print('outer函数中的num:',count)
return inner
counter = outer()
print(counter()) #输出None
运行结果:

def outer():
count = 0
def inner():
nonlocal count #声明count不是局部变量,也不是全局变量,⽽是外部函数outer的局部变量
count = 10 # 直接修改outer函数中的count变量
print('outer函数中的num:',count)
inner()
print('outer函数中的num:',count)
return inner
counter = outer()
print(counter()) #输出None
运行结果:

count = 100
def outer():
count = 0
def inner():
global count # 声明count全局变量,⽽不是外部函数outer的局部变量
count = 10 # 直接修改outer函数中的count变量
print('outer函数中的num:',count)
inner()
print('outer函数中的num:',count)
return inner
counter = outer()
print(counter()) #输出None,函数外部没有count这个变量
print(count) # 外部全局变量的count本来为100被修改为了10
运行结果:
5. 闭包的综合案例
闭包的一个典型应用场景是实现函数调用次数累加器。通过闭包,我们可以在全局作用域中保留计数器的状态。
实例代码:
def outer():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner
f = outer()
f() # 输出1
f() # 输出2
f() # 输出3
f() # 输出4
运行结果:
实例代码:
def counter():
result = 0
def inner(num):
nonlocal result
result += num
print(result)
return inner
f = counter()
f(1) # 输出 1
f(2) # 输出 3
f(3) # 输出 6
运行结果:
在这个例子中,counter
函数返回了 inner
函数的引用。每次调用 f(num)
时,inner
函数都会修改 result
变量的值,并且这个值会被保留下来。
二、装饰器
1. 什么是装饰器?
装饰器(Decorator)是Python中一种非常强大的工具,它允许我们在不修改原函数代码的情况下,为函数添加新的功能。装饰器的本质是一个闭包函数,它接收一个函数作为参数,并返回一个新的函数。
2. 装饰器的雏形
装饰器的核心思想是:在不改变原函数调用方式的情况下,为函数添加新功能。我们可以通过闭包来实现装饰器。
示例代码:
# 要求:把登录功能封装起来(比如封装成一个函数,添加这个登录不能影响现有功能函数)
'''
装饰器:本质是一个闭包,有嵌套、有引用、有返回(返回的是函数的内存地址)
参数 fn 在 check 中也是一个局部变量
参数 fn:就是要装饰的函数的函数名,如 comment,如 download
'''
# 定义装饰器函数 check
def check(fn):
def inner():
# 开发登录功能
print('登录功能')
# 调用原函数
fn() # 调用 comment(), download()
return inner # 返回 inner 函数的内存地址
# 评论功能(前提:登录)
def comment():
print('评论功能')
# 使用装饰器 check 装饰 comment 函数
comment = check(comment) # 调用 check(),把 comment 函数的内存地址赋值给 inner
comment() # 调用 inner 函数,输出:登录功能 评论功能
# 下载功能(前提:登录)
def download():
print('下载功能')
# 使用装饰器 check 装饰 download 函数
download = check(download) # 调用 check(),把 download 函数的内存地址赋值给 inner
download() # 调用 inner 函数,输出:登录功能 下载功能
运行结果:
在这个例子中,check
函数是一个装饰器,它接收一个函数 fn
作为参数,并返回一个新的函数 inner
。inner
函数首先执行登录功能,然后调用原函数 fn
。
3. 装饰器的定义
Python提供了 @
语法糖来简化装饰器的使用。我们可以直接在函数定义前使用 @装饰器名称
来应用装饰器。
示例代码:
def check(fn):
def inner():
print("登录功能")
fn()
return inner
@check # 使用装饰器
def comment():
print("评论功能")
comment() # 输出:登录功能 评论功能
运行结果:
在这个例子中,@check
语法糖相当于 comment = check(comment)
,它自动将 comment
函数传递给 check
装饰器。
4. 装饰器的作用:获取程序的执行时间
装饰器的一个常见应用是获取函数的执行时间。我们可以通过装饰器在不修改原函数代码的情况下,为函数添加计时功能。
示例代码:
import time
def get_time(fn):
def inner():
begin = time.time()
fn()
end = time.time()
print(f"函数执行时间: {end - begin} 秒")
return inner
@get_time
def demo():
for i in range(1000000000):
pass
demo()
运行结果:
在这个例子中,get_time
装饰器为 demo
函数添加了计时功能。
5. 带有参数的装饰器
实例代码:
'''
带有参数的装饰器:① 有嵌套 ② 有引用 ③ 有返回
'''
# 定义装饰器函数 logging
def logging(fn):
def inner(*args, **kwargs):
# 添加装饰器代码(输出日志信息)
print('-- 日志信息:正在努力计算 --')
# 执行要修饰的函数
fn(*args, **kwargs) # 调用 sum_num(a, b)
return inner # 返回 inner 函数的内存地址
# 使用装饰器 logging 装饰 sum_num 函数
@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、通用版本装饰器
为了支持各种类型的函数(无论是否有参数或返回值),我们可以编写一个通用装饰器。通用装饰器可以处理任意数量的参数,并且支持返回值。
示例代码:
def logging(fn):
def inner(*args, **kwargs):
print("正在执行函数")
return fn(*args, **kwargs)
return inner
@logging
def sum_num(a, b):
return a + b
@logging
def sub_num(a, b):
return a - b
print(sum_num(10, 20)) # 输出:正在执行函数 30
print(sub_num(100, 80)) # 输出:正在执行函数 20
运行结果:
8、装饰器⾼级:使⽤装饰器传递参数
有时候我们需要为装饰器传递参数,以便根据不同的参数执行不同的操作。我们可以通过在装饰器外部再包裹一层函数来实现。
实现代码:
'''
通⽤装饰器:① 有嵌套 ② 有引⽤ ③ 有返回 ④ 有不定⻓参数 ⑤ 有return返回值
真正问题:通过装饰器传递参数,我们应该如何接收这个参数呢?
答:在logging⽅法的外侧在添加⼀个函数,专⻔⽤于接收传递过来的参数
'''
def logging(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):
return a + b
@logging('-')
def sub_num(a, b):
return a - b
print(sum_num(10, 20)) # 输出:正在执行加法运算 30
print(sub_num(100, 80)) # 输出:正在执行减法运算 20
运行结果:
1. 装饰器的结构
这个装饰器 logging
是一个带参数的装饰器,它的结构分为三层:
-
外层函数
logging(flag)
:-
接收一个参数
flag
,用于决定装饰器的行为(比如是加法还是减法)。 -
返回
decorator
函数。
-
-
中层函数
decorator(fn)
:-
接收一个函数
fn
(即被装饰的函数,如sum_num
或sub_num
)。 -
返回
inner
函数。
-
-
内层函数
inner(*args, **kwargs)
:-
这是实际执行的函数,它会根据
flag
的值执行不同的逻辑(比如打印“正在执行加法运算”或“正在执行减法运算”)。 -
最后调用原函数
fn(*args, **kwargs)
并返回其结果。
-
三、类装饰器
除了函数装饰器,Python还支持类装饰器。类装饰器通过定义一个类来实现装饰器的功能。
示例代码:
class Check:
def __init__(self, fn):
self.__fn = fn
def __call__(self, *args, **kwargs):
print("请先登录")
self.__fn(*args, **kwargs)
@Check
def comment():
print("评论功能")
comment() # 输出:请先登录 评论功能
在这个例子中,Check
类是一个类装饰器。它通过 __call__
方法使得类的实例可以像函数一样被调用。
总结:
通过本文的学习,你应该已经掌握了Python中闭包和装饰器的核心概念和使用方法。闭包和装饰器是Python中非常强大的工具,它们可以帮助你编写更加灵活和可维护的代码。
关键点回顾:
-
闭包的核心是保留函数执行时的上下文环境。
-
装饰器可以在不修改原函数代码的情况下,为函数添加新功能。
-
通用装饰器可以处理任意数量的参数,并且支持返回值。
-
类装饰器通过
__call__
方法实现装饰器的功能。
希望这篇博客能够帮助你更好地理解Python中的闭包和装饰器,并在实际项目中灵活运用它们。如果你有任何问题或建议,欢迎在评论区留言!
如果你觉得这篇博客对你有帮助,别忘了点赞、收藏和分享!关注我,获取更多Python编程技巧和高级教程。让我们一起在编程的世界中不断进步!