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

Python进阶03-闭包和装饰器

零、文章目录

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 => 1def inner():print(num2)return innerfn = outer()  # 把outer函数执行结果赋值给全局变量fn
fn()          # 找到inner函数,然后执行其内部的代码 => inner => num2 => 100
  • 函数在内存中的存储形式
'''
有一个函数,比如叫def outer():
到底print(outer)和打印print(outer())到底有什么区别?
'''
def outer():return 100print(outer())  # outer()真正含义:找到outer函数在内存中的地址并立即执行其内部的代码
print(outer)    # 只返回outer指向的内存地址,但是其内部的代码并没有执行
(3)修改闭包内的外部变量
  • global :声明全局变量,代表从这行代码开始,使用的变量都是全局中的变量
'''
global :声明全局变量,代表从这行代码开始,使用的变量都是全局中的变量
'''
num = 10
def func():# 尝试在局部作用域中修改全局变量global numnum = 100func()
print(num)
  • nonlocal :声明离它最近的外层的局部变量
'''
nonlocal :声明离它最近的外层的局部变量
'''
def outer():# 局部变量num = 10def inner():nonlocal numnum = 100inner()print(num)  # 100outer()
(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 = 0def inner(num):nonlocal resultresult += numprint(result)return innerf = 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 timedef 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 += iprint(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) = resultreturn inner@logging
def sub_num(a, b):result = a - breturn resultprint(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 + breturn resultprint(sum_num1(20, 10))@logging
def sum_num2(a, b, c):result = a + b + creturn resultprint(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 innerreturn decorator@logging('+')
def sum_num(a, b):result = a + breturn result@logging('-')
def sub_num(a, b):result = a - breturn resultprint(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()

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • PyTorch 基础学习(14)- 归一化
  • 二极管、电阻、电容、电感的种类及作用
  • PHP EOF(heredoc) 速成技巧
  • 如何借助前端表格控件实现软硬一体化数据管理平台
  • 深度学习速通系列:贝叶思SVM
  • spring boot 根据实体类生成表
  • 背包习题
  • CSS 中高度 100%和高度 100vh 有什么区别
  • 第二证券:静态市盈率与动态市盈率有什么区别?
  • 区块链(币圈)常用网址大全
  • STM32F411 标准库硬件SPI (硬件NSS/CS)驱动st7735--1.8寸TFT显示屏
  • 搭建自己的金融数据源和量化分析平台(八):解析PDF财报中的资产负债表
  • 深入浅出神经网络-学习小结
  • 大数据技术之Flume 企业开发案例——负载均衡和故障转移(6)
  • 行为模式6.备忘录模式------文本的撤销和保存
  • JavaScript-如何实现克隆(clone)函数
  • #Java异常处理
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Android组件 - 收藏集 - 掘金
  • Angular 2 DI - IoC DI - 1
  • golang中接口赋值与方法集
  • mongo索引构建
  • overflow: hidden IE7无效
  • PHP的Ev教程三(Periodic watcher)
  • React16时代,该用什么姿势写 React ?
  • unity如何实现一个固定宽度的orthagraphic相机
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 聊聊flink的BlobWriter
  • 马上搞懂 GeoJSON
  • 前端自动化解决方案
  • 如何利用MongoDB打造TOP榜小程序
  • 使用 Docker 部署 Spring Boot项目
  • 思否第一天
  • 微服务核心架构梳理
  • 我建了一个叫Hello World的项目
  • 小程序开发中的那些坑
  • 学习使用ExpressJS 4.0中的新Router
  • 正则与JS中的正则
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • (9)目标检测_SSD的原理
  • (day 12)JavaScript学习笔记(数组3)
  • (Java数据结构)ArrayList
  • (二)斐波那契Fabonacci函数
  • (附源码)spring boot校园健康监测管理系统 毕业设计 151047
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (四)进入MySQL 【事务】
  • (算法二)滑动窗口
  • (转)为C# Windows服务添加安装程序
  • (转)用.Net的File控件上传文件的解决方案
  • .Mobi域名介绍
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例