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

Python 闭包与装饰器

前言:之前讲完了面向对象的三大特征,这篇讲解的是闭包与装饰器(作用域,nonlocal关键字,global关键字)

闭包

定义

闭包是指有权访问另一个函数作用域中变量的函数。简单来说,即使该函数已经执行完毕,其作用域内的变量也不会被销毁,而是会被闭包所引用,从而可以在函数外部继续访问这些变量。

形成条件

要形成一个闭包,通常需要满足以下几个条件:

  1. 嵌套函数:在一个函数内部定义另一个函数。
  2. 内部函数引用外部函数的变量:内部函数使用了外部函数作用域中的变量。
  3. 外部函数返回内部函数:外部函数将内部函数作为返回值返回。

作用

  1. 读取函数内部的变量:闭包可以让我们在函数外部访问函数内部的变量。
  2. 让这些变量的值始终保持在内存中:由于闭包引用了外部函数的变量,这些变量不会随着外部函数的执行结束而被销毁。

示例代码:

'''
闭包程序三步⾛:① 有嵌套 ② 有引⽤ ③ 有返回
'''
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()
装饰器代码的执行流程:
1. check 是一个装饰器函数,它接收一个函数 fn 作为参数。
2. check 内部定义了一个名为 inner 的嵌套函数,它首先执行登录验证功能,然后调用传入的原 始函数 fn
3. 使用 @check 语法糖将装饰器应用于 comment 函数。
4. 当调用 comment() 时,实际上是调用了装饰器 check 内部的 inner 函数。
5. inner 函数先打印’验证登录’,然后调用原始的 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()
输出结果

闭包与装饰器到这里就讲完啦

相关文章:

  • Softhsm储存安全数据性能整理
  • Swift 的 KeyPath 是什么?
  • NS6116-同步降压稳压器 3.1A输出电流 耐压30V
  • 测试方案整理
  • 【原理图PCB专题】自制汉字转码工具,适配Allgero 17版本 Skill
  • C++模板进阶
  • DeepSeek人工智能AI汽车营销销售培训讲师培训师唐兴通讲课汽车销售大数据存量客户数字化营销数字化销售大模型销售话术引流内容社群私域
  • SQL Server安装流程
  • 数据结构-单链表
  • 华为2288H V5服务器无法启动问题处理
  • mysql之如何获知版本
  • Cookie:网页浏览背后的“小秘密”
  • 紫光展锐蜂窝物联网芯片V8850荣获国密一级安全认证
  • Java 设计模式之迭代器模式
  • 头歌实验--面向对象程序设计
  • Kubernetes (k8s) 常用指令速查表
  • 飞书专栏-TEE文档
  • 在 Ubuntu 20.04 为 Clash Verge AppImage 创建桌面图标教程
  • vue2 多页面pdf预览
  • UI自动化测试的优缺点?
  • 无锡网站建设设计公司/深圳新闻最新事件
  • 赤峰市做网站建设的公司/百度推广工作怎么样
  • 做网站便宜/收录情况
  • 什么是网络营销设计/淘宝关键词优化技巧教程
  • 网站建设补充协议模板/今日新闻最新头条10条
  • 肥城市住房和城乡建设厅网站/微信营销推广软件