当前位置: 首页 > news >正文

Python入门13:Python闭包与装饰器

前言:

你是否曾在Python编程中遇到过这样的问题:如何在函数执行后保留局部变量?如何在不修改原函数代码的情况下为其添加新功能?这些问题都可以通过Python中的闭包装饰器来解决!

本文将带你深入理解Python闭包和装饰器的核心概念,并通过大量代码示例帮助你掌握这些高级编程技巧。无论你是Python初学者,还是有一定经验的开发者,这篇博客都将为你提供宝贵的学习资源。

你将学到:

  1. 什么是闭包?闭包的构成条件和实际应用。

  2. 装饰器的定义、作用及如何使用装饰器为函数添加新功能。

  3. 如何编写通用装饰器,支持参数传递和返回值处理。

  4. 高级装饰器技巧:类装饰器的使用。

通过本文的学习,你将能够轻松应对Python中的闭包和装饰器问题,提升代码的可维护性和扩展性。快来一起探索Python的高级编程世界吧!

一、闭包

1、为什么要学闭包?

下面将会通过一个例子为你解释学习闭包的重要性:

# 全局作⽤域(全局变量)
num1 = 10
def func():
    # 局部作⽤域(局部变量)
    num2 = 20
# 调⽤函数
func()
# 在全局作用域中调用全局变量
print(num1)
# 在全局作⽤域中调⽤局部变量num2
print(num2)

运行结果: 

 通过这个例子,我们发现函数执行结束之后我们并不能够获取函数内部的局部变量。主要原因在于,在Python的底层存在⼀个“垃圾回收机制”,主要的作⽤就是回收内存空间。加快计 算机的运⾏。我们在Python代码中定义的变量也是需要占⽤内存的,所以Python为了回收已经被已经过的内存,会⾃动将函数运⾏以后的内部变量和程序直接回收,这个时候我们就可以通过闭包来解决这种问题了。

2. 什么是闭包?

闭包(Closure)是Python中一个非常重要的概念,尤其是在函数式编程中。简单来说,闭包是指在一个函数内部定义的函数,并且这个内部函数引用了外部函数的变量。闭包的核心作用是保留函数执行时的上下文环境,即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的变量。

3. 闭包的构成条件

闭包的构成需要满足以下三个条件:

  1. 有嵌套:在一个函数内部定义了另一个函数。

  2. 有引用:内部函数引用了外部函数的变量。

  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、在闭包的内部实现对外部变量的修改

情景1:直接修改
实例代码:
def outer():
    count = 0
    def inner():
        count = 10
    print('outer函数中的num:',count)
    inner()
    print('outer函数中的num:',count)
    return inner

counter = outer()
print(counter()) #输出None

运行结果:

情景2:使⽤ nonlocal关键字(在函数内部修改函数外部的变量,这个变量⾮全局变量)
实例代码:
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
运行结果:
情景3:使⽤ global关键字(在函数内部声明变量,代表引⽤全局作⽤域中的全局变量)
实例代码:
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 作为参数,并返回一个新的函数 innerinner 函数首先执行登录功能,然后调用原函数 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 是一个带参数的装饰器,它的结构分为三层:

  1. 外层函数 logging(flag)

    • 接收一个参数 flag,用于决定装饰器的行为(比如是加法还是减法)。

    • 返回 decorator 函数。

  2. 中层函数 decorator(fn)

    • 接收一个函数 fn(即被装饰的函数,如 sum_num 或 sub_num)。

    • 返回 inner 函数。

  3. 内层函数 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中非常强大的工具,它们可以帮助你编写更加灵活和可维护的代码。

关键点回顾:

  1. 闭包的核心是保留函数执行时的上下文环境

  2. 装饰器可以在不修改原函数代码的情况下,为函数添加新功能。

  3. 通用装饰器可以处理任意数量的参数,并且支持返回值。

  4. 类装饰器通过 __call__ 方法实现装饰器的功能。

希望这篇博客能够帮助你更好地理解Python中的闭包和装饰器,并在实际项目中灵活运用它们。如果你有任何问题或建议,欢迎在评论区留言!


如果你觉得这篇博客对你有帮助,别忘了点赞、收藏和分享!关注我,获取更多Python编程技巧和高级教程。让我们一起在编程的世界中不断进步!

相关文章:

  • 渗透测试(WAF过滤information_schema库的绕过,sqllib-46关,海洋cms9版本的注入)
  • Discourse 中集成 Claude 3.7 Sonnet 模型
  • AutoMQ 可观测性实践:如何使用 OpenTelemetry 监控 Kafka 和底层流存储
  • 从“Switch-case“到“智能模式“:C#模式匹配的终极进化指南
  • Vue 2 新手入门指南
  • 如何在docker上部署前端nginx服务(VUE)
  • dex2oat配置方法及优化指南
  • 【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
  • 关于在java项目部署过程MySQL拒绝连接的分析和解决方法
  • ubuntu上boost卸载和安装
  • 大模型自动提示优化(APO)综述笔记
  • Win10开启电脑自带录屏截图功能
  • 学Java第三十一天----------多态调用成员的特点
  • FastAPI系列:如何响应txt和json文件
  • 回溯算法(C/C++)
  • 萌新学 Python 之模块管理
  • cursor设备ID修改器 支持0.45.x版本
  • Qt Creator + CMake 构建教程
  • 述职报告中,如何平衡展现个人贡献与团队协作的重要性?
  • Python 函数式编程-偏函数
  • 诺和诺德一季度减重版司美格鲁肽收入增83%,美国市场竞争激烈下调全年业绩预期
  • 习近平在俄罗斯媒体发表署名文章
  • 潘功胜:降准0.5个百分点,降低政策利率0.1个百分点
  • 上海乐高乐园明天正式开售年卡,下月开启试运营
  • 沪幼升小网上报名明起开始,是否参与民办摇号怎么定?
  • 印度扩大对巴措施:封锁巴基斯坦名人账号、热门影像平台社媒