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()
输出结果

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/969622.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Flink快速入门-4.流处理之基于 Key 的算子】

流处理之基于 Key 的算子 实验介绍 在 SQL 中我们经常会用到分组&#xff08;group by&#xff09;操作&#xff0c;在 group 关键词之后指定要聚合的键&#xff0c;在 group 之前指定要聚合的逻辑&#xff08;计数、求和、求最大值等&#xff09;&#xff0c;通过分区键将数…

sib报错:com.*.xctrunner is not in your device!

1、问题描述 在使用sonic集成IOS设备的时候,我们需要通过sonic-agent服务去识别IOS设备。但是在识别的时候提示如下问题: 本质就是在你这个设备中找不到这个设备也就是找不到WebDriverAgentRunner,但是确实安装了,甚至appium可以正常的调用。 或执行如下命令的时候报错:…

数据结构与算法-队列

参考学习&#xff1a;B站-逊哥带你学编程 队列的定义与实现 队列的顺序结构实现 #define MAXSIZE 100 typedef int ElemType;typedef struct {ElemType data[MAXSIZE];int front;int rear; }Queue;图示&#xff1a; 队列的顺序结构-初始化 void initQueue(Queue *Q) {Q->…

SQL联合查询

文章目录 MySQL系列&#xff1a;1.内连接2.外连接3.自连接4.子查询5.合并查询6.插入查询 MySQL系列&#xff1a; 初识MySQL&#xff0c;MySQL常用数据类型和表的操作&#xff0c;增删改查(CRUD)操作(总),数据库约束数据库设计 #班级表 drop table if exists class; create ta…

急停信号的含义

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#的运动控制程序的时候&#xff0c;一个必要的步骤就是确认设备按钮的急停…

哈希表-三数之和

代码随想录-刷题笔记 15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 内容&#xff1a; 这道题讲真真挺有意思的。双指针的用法很巧妙&#xff0c;而且去重的细节多到离谱。 哈希表本身的做法我没搞懂&#xff0c;而且确实复杂的很。既然有更好的方法就一步到位 本…

边缘计算网关功能优势及带来的数据处理变化

边缘计算是一种分布式计算架构&#xff0c;其核心思想是将数据处理、存储和服务功能移近数据产生的边缘位置&#xff0c;即接近数据源和用户的位置&#xff0c;而不是依赖中心化的数据中心或云计算平台。这种计算模式通过在靠近终端设备的位置进行数据处理&#xff0c;旨在降低…

8D系统与FMEA系统软件的关联性分析

8D系统与FMEA系统的关联性分析 在质量管理中&#xff0c;8D和FMEA是两种重要的工具&#xff0c;它们在功能上相互补充&#xff0c;形成了一个高效的质量管理闭环。 FMEA&#xff08;潜在失效模式及后果分析&#xff09; 是一种预防性工具&#xff0c;用于在产品设计和制造过程…

java面试题-集合篇

Collection 1.Collection有哪些类&#xff1f; Java集合框架中的Collection接口是所有集合类的基础接口&#xff0c;定义了一些基本的集合操作&#xff0c;如添加元素、删除元素、判断是否包含某个元素等。常见的集合类包括List、Set和Queue。 List List接口定义了按照索引…

学习总结2.14

深搜将题目分配&#xff0c;如果是两个题目&#xff0c;就可以出现左左&#xff0c;左右&#xff0c;右左&#xff0c;右右四种时间分配&#xff0c;再在其中找最小值&#xff0c;即是两脑共同处理的最小值 #include <stdio.h> int s[4]; int sum0; int brain[25][25]; …

GESP C++ 三级真题(2024年12月)T2打印数字

3.2 编程题 2 题目&#xff1a; 打印数字 时间限制&#xff1a; 1.0 s 内存限制&#xff1a; 512.0 MB 3.2.1 题面描述 小杨为数字 0, 1, 2 和 3 设计了一款表示形式&#xff0c;每个数字占用了 ( 5 \times 5 ) 的网格。数字 0, 1, 2 和 3 的表示形式如下&#xff1a; 0: ..…

【16届蓝桥杯寒假刷题营】第1期DAY4

4.可达岛屿的个数 - 蓝桥云课 题目背景 在一个神奇的魔法世界中&#xff0c;有一座古老的迷幻之城。迷幻之城被分成 n 个鸟屿&#xff0c;编号从 1 到 n&#xff0c;共有 m 座桥。迷幻之城的居民们希望能够建立起紧密的联系&#xff0c;每个岛屿上的居民都想知道自己最多能到…

谭浩强C语言程序设计(5) 9章

1、统计三个候选人的票数 #include <cstdio> // 引入标准输入输出库 #include <cstring> // 引入字符串处理库&#xff0c;用于 strcmp 函数 #define N 10 // 定义一个宏常量 N&#xff0c;表示数组的最大长度// 定义一个结构体 Person&#xff0c;用于存储…

【第5章:深度生成模型— 5.2 图像生成实战:DCGAN、StyleGAN等模型的实现与优化】

在人工智能领域,生成对抗网络(GAN)绝对是近十年最令人兴奋的技术突破之一。想象一下,你给电脑看一万张人脸照片,它就能凭空创造出不存在的人脸,这种魔法般的创造力让无数开发者着迷。今天我们就来深入探讨这个领域的两大经典模型:DCGAN和StyleGAN,用代码+实战的方式,把…

数仓:核心概念,数仓系统(ETL,数仓分层,数仓建模),数仓建模方法(星型模型,雪花模型,星座模型)和步骤

数仓建模的核心概念 事实表&#xff08;Fact Table&#xff09;&#xff1a; 存储业务过程的度量值&#xff08;如销售额、订单数量等&#xff09;。 通常包含外键&#xff0c;用于关联维度表。 维度表&#xff08;Dimension Table&#xff09;&#xff1a; 存储描述性信息&…

咪头(采集声音的设备)差分输入与单端输入有什么区别

咪头&#xff08;采集声音的设备&#xff09;差分输入与单端输入有什么区别 答案摘自chatgpt 咪头&#xff08;麦克风&#xff09;在采集声音时&#xff0c;通常会有两种输入方式&#xff1a;差分输入&#xff08;Differential Input&#xff09; 和 单端输入&#xff08;Singl…

左移架构 -- 从攒批,湖仓到使用数据流的实时数据产品

编辑导读: 这篇文章翻译自 Kai Waehner的 《The Shift Left Architecture – From Batch and Lakehouse to Real-Time Data Products with Data Streaming》。文章通过数据产品的概念引出了如何创建可重复使用的数据产品使企业能够从当前和未来的数据中获得价值。基于构建数据产…

DBeaver clickhouse 时区不对 时间少了8小时

选择DataBase选择Driver Manager选择clickhouse数据库点中之后&#xff0c;选择编辑添加两个全局属性 use_server_time_zone use_time_zone 鼠标移动到User Properties上&#xff0c;右键即可添加一列空白 然后断开重连

DeepSeek 背后的技术:GRPO,基于群组采样的高效大语言模型强化学习训练方法详解

强化学习&#xff08;Reinforcement Learning, RL&#xff09;已成为提升大型语言模型&#xff08;Large Language Models, LLMs&#xff09;推理能力的重要技术手段&#xff0c;特别是在需要复杂推理的任务中。DeepSeek 团队在 DeepSeek-Math [2] 和 DeepSeek-R1 [3] 模型中的…

django上传文件

1、settings.py配置 # 静态文件配置 STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR /static, ]上传文件 # 定义一个视图函数&#xff0c;该函数接收一个 request 参数 from django.shortcuts import render # 必备引入 import json from django.views.decorators.http i…