Python进阶03-闭包和装饰器

news2024/12/27 5:02:19

零、文章目录

Python进阶03-闭包和装饰器

1、作用域

(1)作用域
  • 在Python代码中,作用域分为两种情况:
    • 全局作用域
    • 局部作用域
(2)变量的作用域
  • 随着函数的出现,作用域被划分为两种
    • 在全局定义的变量 => 全局变量

    • 在局部定义的变量 => 局部变量

(3)变量的访问范围
  • 全局变量的访问范围:

    • ① 在全局作用域中可以正常访问全局变量
    • ② 在局部作用域中也可以正常访问全局变量
  • 局部变量的访问范围:

    • ① 在局部作用域中可以正常访问局部变量
    • ② 在全局作用域中无法正常访问局部变量
  • 代码演示

    • 在全局作用域中可以访问全局变量,在局部作用域中可以访问局部变量
    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        num2 = 20
        # ① 在局部访问局部变量
        print(num2)
    
    # ① 在全局访问全局变量
    print(num1)
    # 调用函数
    func()
    
    • ② 在局部作用域中可以访问全局变量
    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        # ② 在局部作用域中可以访问全局变量
        print(num1)
    
    # 调用函数
    func()
    
    • ③ 在全局作用域中不能访问局部变量
    # 全局作用域(全局变量)
    num1 = 10
    def func():
        # 局部作用域(局部变量)
        num2 = 20
    
    # 调用函数
    func()
    # 在全局作用域中调用局部变量num2
    print(num2)
    

    image-20240817154149761

2、闭包

(1)闭包的定义
  • 为什么在全局作用域中无法访问局部变量?

    • 主要原因在于,在Python的底层存在一个“垃圾回收机制”,作用就是回收内存空间,加快计算机的运行。我们在Python代码中定义的变量也是需要占用内存的,所以Python为了回收已经被已经过的内存,会自动将函数运行以后的内部变量和程序直接回收。
  • 我们有没有办法把函数内部的局部变量保留?

    • 使用闭包,闭包定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数的地址,我们把这个使用外部函数变量的内部函数称为闭包。
(2)闭包的构成条件
  • 闭包的构成条件
    • ① 在函数嵌套(函数里面再定义函数)的前提下
    • ② 内部函数使用了外部函数的变量(还包括外部函数的参数)
    • ③ 外部函数返回了内部函数
  • 注意点:由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
'''
问题:我们在函数执行完毕后,能不能把函数内部的局部变量保存内存空间中?
答:可以使用闭包来实现这个操作
问题:如何把官方的闭包掌握下来?闭包三步走
答:① 有嵌套 ② 有引用 ③ 有返回
闭包两个方面:① 遇到闭包案例,知道如何解题 ② 能使用闭包编写Python装饰器
'''
def outer():
    num2 = 100  # 默认计数器为0 => +1 => 1
    def inner():
        print(num2)
    return inner

fn = outer()  # 把outer函数执行结果赋值给全局变量fn
fn()          # 找到inner函数,然后执行其内部的代码 => inner => num2 => 100
  • 函数在内存中的存储形式
'''
有一个函数,比如叫def outer():
到底print(outer)和打印print(outer())到底有什么区别?
'''
def outer():
    return 100

print(outer())  # outer()真正含义:找到outer函数在内存中的地址并立即执行其内部的代码
print(outer)    # 只返回outer指向的内存地址,但是其内部的代码并没有执行
(3)修改闭包内的外部变量
  • global :声明全局变量,代表从这行代码开始,使用的变量都是全局中的变量
'''
global :声明全局变量,代表从这行代码开始,使用的变量都是全局中的变量
'''
num = 10
def func():
    # 尝试在局部作用域中修改全局变量
    global num
    num = 100

func()
print(num)
  • nonlocal :声明离它最近的外层的局部变量
'''
nonlocal :声明离它最近的外层的局部变量
'''
def outer():
    # 局部变量
    num = 10
    def inner():
        nonlocal num
        num = 100
    inner()
    print(num)  # 100

outer()
(4)闭包综合案例
'''
闭包编写三步走:① 有嵌套 ② 有引用 ③ 有返回
分析:
执行f = func()的时候,result赋值为0,然后定义inner,返回inner,最终结果f = inner函数的内存地址
执行f(1),相当于执行inner函数,nonlocal引用局部变量result=0,然后进行+1操作,弹出0+1=1
继续执行
执行f(2),相当于执行inner函数,声明nonlocal result,代表还是引用外部的局部变量,由于此时外部的result已经被
f(1)更改为1了,所以由于局部变量一直没有消失,所以此时result=1,执行+2操作,最终结果为3
'''
def func():
    result = 0
    def inner(num):
        nonlocal result
        result += num
        print(result)
    return inner

f = func()
f(1)  # 1
f(2)  # 3

3、装饰器

(1)什么是装饰器
  • 装饰器:在不改变现有函数源代码以及函数调用方式的前提下,实现给函数增加额外的功能。

  • 装饰器的本质:就是一个闭包函数(三步:① 有嵌套 ② 有引用 ③ 有返回)

(2)装饰器代码实现原理
# 要求:把登录功能封装起来(比如封装成一个函数,添加这个登录不能影响现有功能函数)
'''
装饰器:本质是一个闭包,有嵌套、有引用、有返回(返回的是函数的内存地址)
参数fn在check中也是一个局部变量
参数fn:就是要装饰的函数的函数名,如comment,如download
'''
def check(fn):
    def inner():
        # 开发登录功能
        print('登录功能')
        # 调用原函数
        fn()
    return inner

# 评论功能(前提:登录)
def comment():
    print('评论功能')

comment = check(comment)
comment()

# 下载功能(前提:登录)
def download():
    print('下载功能')

download = check(download)
download()
(3)装饰器定义
'''
装饰器:本质就是一个闭包 ① 有嵌套 ② 有引用 ③ 有返回
'''
def check(fn):
    
    def inner():
        # 开发登录验证功能
        print('验证登录')
        # 执行原有函数
        fn()
    return inner

@check
def comment():
    print('发表评论')

comment()
(4)装饰器案例:获取程序的执行时间
'''
定义获取程序的执行时间装饰器 => 闭包(① 有嵌套 ② 有引用 ③ 有返回)
'''
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()
(5)装饰器带参数
'''
带有参数的装饰器:① 有嵌套 ② 有引用 ③ 有返回
'''
def logging(fn):
    def inner(*args, **kwargs):
        # 添加装饰器代码(输出日志信息)
        print('-- 日志信息:正在努力计算机 --')
        # 执行要修饰的函数
        fn(*args, **kwargs)  # sum_num(a, b)
    return inner

@logging
def sum_num(*args, **kwargs):
    result = 0
    # *args代表不定长元组参数,args = (10, 20)
    for i in args:
        result += i
    # **kwargs代表不定长字典参数, kwargs = {a:30, b:40}
    for i in kwargs.values():
        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)装饰器通用版本
'''
通用装饰器:① 有嵌套 ② 有引用 ③ 有返回 ④ 有不定长参数 ⑤ 有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))
(8)装饰器传递参数
  • 基本语法:
def 装饰器(fn):
    ...

@装饰器('参数')
def 函数():
    # 函数代码
  • 案例:根据传递参数不同,打印不同的日志信息
'''
通用装饰器:① 有嵌套 ② 有引用 ③ 有返回 ④ 有不定长参数 ⑤ 有return返回值
真正问题:通过装饰器传递参数,我们应该如何接收这个参数呢?
答:在logging方法的外侧在添加一个函数,专门用于接收传递过来的参数
'''

def logging(flag):
    # flag = + 或 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):
    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))
(9)类装饰器
  • 基本语法:
class 类装饰器():
    # 装饰器代码

@类装饰器名称
def 函数():
    # 函数代码
  • 案例:编写一个Check类装饰器,用于实现用户的权限验证
'''
类装饰器编写规则:
① 必须有一个__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()

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

江协科技STM32学习- P7 GPIO输入

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…

docker安装AWVS15(网络拉取失败,提供百度云镜像下载)

一.背景 准备在服务器上安装AWVS15用于扫描,直接拉取一直提示网络错误,刚好本地上有容器,就直接将本地的AWVS容器打包上传了,顺带上传到百度云来避免今后直接拉取网络出错的情况。考虑到其他师傅可能也会遇到相似问题&#xff0c…

最新高仿拼夕夕源码/拼单系统源码/拼单商城/类目功能齐全

源码简介: 高仿拼夕夕源码,拼单商城系统源码、拼团商城源码,改的版本。拼夕夕拼团商城系统源码源码 多商户多区域拼团系统源码。 自己改的版本,类似于拼单的商城,功能齐全,看着还挺不错,绝对值…

上新!Matlab实现基于QRGRU-Attention分位数回归门控循环单元注意力机制的时间序列区间预测模型

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现基于QRGRU-Attention分位数回归门控循环单元注意力机制的时间序列区间预测模型; 2.多图输出、多指标输出(MAE、RMSE、MSE、R2),多输入单输出,含不同置信区间图、概率…

多任务学习MTL模型:多目标Loss优化策略

前言 之前的文章中多任务学习MTL模型:MMoE、PLE,介绍了针对多任务学习的几种模型,着重网络结构方面的优化,减缓task之间相关性低导致梯度冲突,模型效果差,以及task之间的“跷跷板”问题。 但其实多任务学…

文件包含之session.upload_progress的使用

目录 原理 环境搭建 渗透 结果 一次项目经历复现 原理 session.auto_start顾名思义,如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,也是通常情况下,这…

k8s声明式管理方式(yaml文件实现)

首先在/opt目录下创建 mkdir k8s-yaml cd k8s-yaml/ yaml文件 1.deployment的部署方式 首先 kubectl explain deployment 获取它的类型kind和标签version vim nginx-deploy.yaml apiVersion: apps/v1 #定义api版本的标签 kind: Deployment #定义资源的类型(kin…

【数模修炼之旅】10 遗传算法 深度解析(教程+代码)

【数模修炼之旅】10 遗传算法 深度解析(教程代码) 接下来 C君将会用至少30个小节来为大家深度解析数模领域常用的算法,大家可以关注这个专栏,持续学习哦,对于大家的能力提高会有极大的帮助。 1 遗传算法介绍及应用 …

网络安全面试经验80篇

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s…

《JavaEE进阶》----5.<SpringMVC②剩余基本操作(CookieSession)>

Cookie和Session简介。 Spring MVC的请求中 Cookie的设置和两种获取方式 Session的设置和三种获取方式。 三、(接上文)SpringMVC剩余基本操作 3.2postman请求 3.2.10 获取Cookie和Session 1.理解Cookie 我们知道HTTP协议自身是“无状态”协议。 &qu…

2024.8.28 C++

使用C手动封装一个顺序表&#xff0c;包含成员数组一个&#xff0c;成员变量N个 代码 #include <iostream> //使用C手动封装一个顺序表&#xff0c;包含成员数组一个&#xff0c;成员变量N个 using namespace std;using datatype int; struct Seqlist { private:datat…

flink 实战理解watermark,maxOutOfOrderness,allowedLateness

watermark watermark的作用 就是延迟触发窗口&#xff0c;让乱序到达的元素依然能够落在正确的窗口内。为啥能实现这个效果&#xff0c;一直通过公式更新watermark,如果乱序到的元素就不能更新watermark,相当于就是延迟触发计算操作。触发时间 watermark 大于窗口的最大值allo…

我的易经代码

本人从2000年起&#xff0c;就开始写一款算命软件&#xff0c;第一版用的是powerbuilder。后来改成企业版&#xff0c;名为“始皇预测”&#xff0c;用Java Swing编写&#xff0c;支持五大神数&#xff0c;三式&#xff0c;主要应用还是六爻、四柱、风水&#xff0c;其它如称骨…

2024118读书笔记|《岳阳楼记》——天高地迥,觉宇宙之无穷;兴尽悲来,识盈虚之有数

2024118读书笔记|《岳阳楼记》——天高地迥&#xff0c;觉宇宙之无穷&#xff1b;兴尽悲来&#xff0c;识盈虚之有数 爱莲说陋室铭小石潭记醉翁亭记赤壁赋桃花源记归去来兮辞木兰辞阿房宫赋滕王阁序岳阳楼记 《岳阳楼记》范仲淹&#xff0c;都是背过的古文&#xff0c;挺不错的…

【Qt窗口】—— 工具栏

前情摘要&#xff1a; 工具栏相当于菜单栏中的众多快捷方式&#xff0c;毕竟很多操作都是通过菜单栏来直接访问的&#xff0c;但是可能会查找很长时间&#xff0c;首先就是查找在哪个菜单里面&#xff0c;打开菜单才能进一步操作。而工具栏则是把一些常用的操作都给列举出来&am…

生产者与消费者模型

生产者与消费者模型 生产者&#xff1a;生产数据的线程&#xff0c;这类的线程负责从用户端、客户端接收数据&#xff0c;然后把数据Push到存储中介。 消费者&#xff1a;负责消耗数据的线程&#xff0c;对生产者线程生产的数据进行&#xff08;判断、筛选、使用、响应、存储&…

C++必修:布隆过滤器的提出与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. 布隆过滤器的引入 在我们注册游戏或者社交账号时&#xff0c;我们可以自己设置…

科学重温柯南TV版:基于B站视频数据分析

麻鸭&#xff0c;四年过去了&#xff0c;失踪人口回归。 第一篇就决定是你了。 看了柯南M27剧场版后&#xff0c;萌生了重温TV版的念头&#xff0c;但是1191集(截止24/8/26)的体量太恐怖了&#xff0c;遂取点巧&#xff0c;综合大V建议(知乎&#xff1b;公众号)和视频网站数据…

基于asp.net的驾校管理系统附源码

这是一个基于asp.net的webform框架开发的BS架构的系统&#xff0c;详情如下&#xff1a; 项目下载链接 链接&#xff1a;https://pan.quark.cn/s/0679e783ef71