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

python进阶篇-day04-闭包与装饰器

day04闭包装饰器

函数参数

函数名作为对象

细节

  1. Python是一门以 面向对象为基础的语言, 一切皆对象, 所以: 函数名也是对象.

  2. 直接打印函数名, 打印的是函数的地址. 函数名()则是在调用函数.

  3. 函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名() 和 调用 原函数名() 效果是一样的.

格式
def func():print('hello word')f = func
f() # hello word

演示

# 需求: 定义1个无返回值的函数func01(), 并直接输出函数名观察效果.
​
# 1. 定义函数.
def func01():print('hello world!')# return None
​
​
# 在main函数中测试
if __name__ == '__main__':# 2. 直接打印函数名, 打印的是函数的地址.  说明 函数名 = 对象print(func01)   # <function func01 at 0x000002069305A200>
​# 3. 写 函数名() 则是在调用函数.func01()        # hello worldprint('-' * 21)print(func01()) # hello world先输出, 再打印Noneprint('-' * 21)
​# 4. 函数名可以作为对象使用, 所以它可以像变量一样赋值, 且赋值后的 变量名()  和 调用 原函数名() 效果是一样的.f = func01      # 把 函数名(函数的地址) 赋值给变量fprint(f)        # <function func01 at 0x000002069305A200>f()             # hello worldprint('-' * 21)print(f())      # hello world先输出, 再打印None

函数名作为实参传递

函数名的作用

  1. 函数名可以作为对象, 像变量那样赋值.

  2. 函数名可以作为实参进行传递.

代码
# 需求: 定义1个无参函数method(), 定义1个带1个参数的函数func(), 把method()的函数名作为参数传递给func(), 并观察结果.
​
# 1. 定义1个无参函数method()
def method():print('我是 method 函数')
​
def get_sum(a, b):print('get_sum函数: 加法运算')return a + b
​
def get_subtract(a, b):print('get_subtract函数: 减法运算')return a - b
​
# 2. 定义1个带1个参数的函数func()
def func(fn_name):"""接收函数名(函数对象), 然后调用该函数的.:param fn_name: 接收到的函数名(即: 函数对象):return: 无"""print('我是 func 函数')fn_name()
​
# 模拟自定义的计算器.
def my_calculation(a, b, fn_name):"""接收两个整数 和 具体的计算规则, 对整数进行对应的计算.:param a: 要操作的第1个整数:param b: 要操作的第2个整数:param fn_name: 具体的计算规则:return: 计算结果."""return fn_name(a, b)
​
# 3. 在main函数中测试.
if __name__ == '__main__':# 演示: 函数名可以作为实参进行传递func(method)print('-' * 21)
​# 加法result1 = my_calculation(10, 3, get_sum)print(f'result1: {result1}')    # 13print('-' * 21)
​# 减法result2 = my_calculation(10, 3, get_subtract)print(f'result2: {result2}')    # 7

闭包

介绍

概述

属于python的一种独有写法, 可以实现,: 对外部变量进行临时存储

回顾

函数内的局部变量在函数调用结束后生命周期结束

作用

延长函数内(外部函数) 局部变量的生命周期

构成条件

  1. 有嵌套 外部函数内嵌套 内部函数

  2. 有引用 在内部函数中使用 外部函数的变量

  3. 有返回 在外部函数中, 返回内部函数名, 即: 内部函数 对象

格式
def 外部函数名(形参列表):        ......def 内部函数名(形参列表):    # 有嵌套......使用外部函数的变量       # 有引用return 内部函数名           # 有返回

入门代码

def fn_outer(num1):def fn_inner(num2):             # 有嵌套sum = num1 + num2           # 有引用print(f'求和结果:{sum}')return fn_inner                 # 有返回
​
​
if __name__ == '__main__':fn = fn_outer(10)fn(10)fn(11)fn(12)
​

执行流程图解

nonlocal介绍

概述

nonlocal: 可以实现在内部函数中, 修改外部函数的 变量值

类似于global关键字

给内部函数赋权, 使其可以修改外部函数的变量值

演示

def fn_outer():a = 100def fn_inner():nonlocal a      # 使用nonlocal关键字a += 10         # 修改外部函数的变量print(f'a:{a}')return fn_inner
​
​
if __name__ == '__main__':fn = fn_outer()fn()    # a:110fn()    # a:120fn()    # a:130
图解

装饰器

在不改变原有函数的基础上给原有函数增加额外的功能

介绍

概述

装饰器也是闭包的一种, 即: 装饰器 = 闭包 + 额外的功能

前提

  1. 有嵌套

  2. 有引用

  3. 有返回

  4. 有额外功能

格式
def 外部函数(形参列表):......def 内部函数(形参列表):功能扩展......使用外函数变量return 内部函数名

用法

方式1: 传统用法

​ 变量名 = 装饰器名(被装饰函数名) => 变量名一般为被装饰函数名

​ 变量名()

方式2: 语法糖

​ 在被装饰的函数上, 写: @装饰器名, 之后跟调用普通函数方式一样

代码演示

# 定义装饰器, 增加登录功能
def check_login(fn_name):def inner():print('登陆中...')fn_name()
​return inner
​
​
# 定义评论功能
@check_login
def comment():print('评论成功!!!')
​
​
if __name__ == '__main__':# 方式1, 传统写法# comment = check_login(comment)# comment()# 方式2, 语法糖comment()
​
​

装饰器的用法

装饰器的内部函数 格式要和 被装饰函数的格式保持一致 即:

被装饰函数是无参无返回值, 则: 装饰器的内部函数 也是 无参无返回值

被装饰函数是有参无返回值, 则: 装饰器的内置函数 也是 有参无返回值

......

无参无返回

# 需求: 在无参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...
​
# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)def fn_inner():         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.print('[友好提示] 正在努力计算中...')  # 有额外功能fn_name()                          # 有引用return fn_inner         # 有返回
​
​
# 2. 要被装饰的函数, 即: 原函数(这里是: 无参无返回值的)
@print_info
def get_sum():a = 10b = 20# 求和sum = a + b# 打印结果.print(f'sum: {sum}')
​
# 在main函数中测试调用.
if __name__ == '__main__':# 装饰器 写法1: 传统写法.# get_sum = print_info(get_sum)# get_sum()
​# 装饰器 写法2: 语法糖get_sum()

有参无返回

# 需求: 在有参无返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...
​
# 1. 装饰器.
def print_info(fn_name):    # fn_name: 要被装饰的函数名(原函数名)def fn_inner(a, b):         # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.print('[友好提示] 正在努力计算中...')  # 有额外功能fn_name(a, b)                      # 有引用return fn_inner         # 有返回
​
​
# 2. 要被装饰的函数, 即: 原函数(这里是: 有参无返回值的)
@print_info
def get_sum(a, b):# 求和sum = a + b# 打印结果.print(f'sum: {sum}')
​
# 在main函数中测试调用.
if __name__ == '__main__':# 装饰器 写法1: 传统写法.# get_sum = print_info(get_sum)# get_sum(100, 200)
​# 装饰器 写法2: 语法糖get_sum(10, 3)

无参有返回

# 需求: 在无参有返回值的原有求和函数前, 添加1个友好提示, 即: 正在努力计算中...
​
# 1. 装饰器.
def print_info(fn_name):  # fn_name: 要被装饰的函数名(原函数名)def fn_inner():  # 有嵌套.  装饰器的内部函数 格式 要和 原函数 格式保持一致.print('[友好提示] 正在努力计算中...')  # 有额外功能return fn_name()  # 有引用
​return fn_inner  # 有返回
​
​
# 2. 要被装饰的函数, 即: 原函数(这里是: 无参有返回值的)
@print_info
def get_sum():a, b = 10, 3# 求和sum = a + b# 返回结果.return sum
​
​
# 在main函数中测试调用.
if __name__ == '__main__':# 装饰器 写法1: 传统写法.# get_sum = print_info(get_sum)# sum1 = get_sum()# print(f'sum1: {sum1}')
​# 装饰器 写法2: 语法糖sum2 = get_sum()print(f'sum2: {sum2}')

有参有返回

# 定义装饰器, 装饰有参数有返回值的函数
def print_info(fn_name):def inner(a, b):print('正在计算中')return fn_name(a, b)
​return inner
​
​
# @print_info
def get_sum(a, b):num_sum = a + breturn num_sum
​
​
if __name__ == '__main__':# 调用方式1: 语法糖# print(get_sum(10, 20))
​# 调用方式2: 传统方式get_sum = print_info(get_sum)print(get_sum(10, 20))

装饰不定长参数

# 定义装饰器装饰带有不定长参数的函数
def print_info(fn_name):def inner(*args, **kwargs):print('正在努力计算中')return fn_name(*args, **kwargs)
​return inner
​
# 定义函数, 同时接收不定长位置参数和关键字参数, 遍历累加求和
@print_info
def get_sum(*args, **kwargs):num_sum = 0# 遍历位置参数并求和for i in args:num_sum += i# 遍历关键字参数并求和for j in kwargs.values():num_sum += jreturn num_sum
​
​
if __name__ == '__main__':# 调用方式1# get_sum = print_info(get_sum)# print(get_sum(1, 2, 3, 4, a=5, b=6, c=7))
​# 调用方式2print(get_sum(1, 2, 3, 4, a=5, b=6, c=7))

多装饰器装饰1个函数

由内到外装饰(传统写法), 从上到下执行(装饰器)

代码
def check_login(fn_name):def inner():print('登录校验中...')fn_name()
​return inner
​
​
def check_code(fn_name):def inner():print('登录校验中...')fn_name()
​return inner
​
​
@check_login
@check_code
def comment():print('评论成功!!!')
​
​
if __name__ == '__main__':# 方式1# cc = check_code(comment)# cl = check_login(cc)# cl()# 方式2comment()
图解

带有参数的装饰器(一个装饰器装饰多个函数)

一个装饰器的参数只能有一个

如果装饰器有多个参数, 则需要在该装饰器的外边再定义一层函数, 用于封装多个参数, 并返回装饰器

格式
def logging(flag):def decorator(fn_name):def inner():if flag == '+':print('正在努力计算加法中')elif flag == '-':print('正在努力计算减法中')fn_name()
​return inner
​return decorator
​
​
@logging('+')
def sum_add():a, b = 1, 2print(f'求和结果为: {a + b}')
​
​
@logging('-')
def subtract():a, b = 1, 2print(f'求和结果为: {a - b}')
​
​
if __name__ == '__main__':sum_add()subtract()
改进

通过对象的__ name __属性实现

* 代码:

def decorator(fn_name):def inner():if fn_name == 'sum_add':print('正在努力计算加法中')elif fn_name == 'subtract':print('正在努力计算减法中')fn_name()return inner
​
​
@decorator
def sum_add():a, b = 1, 2print(f'求和结果为: {a + b}')
​@decorator
def subtract():a, b = 1, 2print(f'求和结果为: {a - b}')
​
​
if __name__ == '__main__':sum_add()subtract()

扩展:类装饰器(了解)

装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。

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()

@Check 等价于 comment = Check(comment), 所以需要提供一个init方法,并多增加一个fn参数。

要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。

call方法里进行对fn函数的装饰,可以添加额外的功能。

目标:① 了解闭包的作用以及闭包的基本语法(三步走)

​ ② 能独立完成通用装饰器的编写

​ ③ 能使用装饰器传递参数

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Springboot快速创建的两种方法(简单易学)
  • UE5 UMG UI编辑器工作流
  • HarmonyOS NEXT未成年人模式无缝联动所有应用,过滤非适龄内容
  • C语言学习笔记 Day15(文件管理--下)
  • 多态,匿名内部类(lambda表达式),集合
  • 【Tools】如何评价黑悟空这款游戏
  • Python中的集合魔法:解锁高效数据处理的秘密
  • 无法连接Redis服务问题排查
  • 云计算实训36——mysql镜像管理、同步容器和宿主机时间、在容器外执行容器内命令、容器的ip地址不稳定问题、基础镜像的制作、镜像应用
  • Question mutiple pdf‘s using openai, pinecone, langchain
  • 新160个crackme - 045-CyTom-crackme
  • 如何用GPT进行编程辅助?
  • MyBatis 源码解析:DefaultSqlSessionFactory 的创建与管理
  • 基于QT与STM32的电力参数采集系统(华为云IOT)(211)
  • 面试经验分享 | 华为安全面试--年薪50万offer
  • .pyc 想到的一些问题
  • 《Java编程思想》读书笔记-对象导论
  • 【刷算法】求1+2+3+...+n
  • canvas绘制圆角头像
  • CSS盒模型深入
  • github从入门到放弃(1)
  • JAVA 学习IO流
  • java中具有继承关系的类及其对象初始化顺序
  • laravel with 查询列表限制条数
  • Linux gpio口使用方法
  • Netty源码解析1-Buffer
  • ReactNativeweexDeviceOne对比
  • VUE es6技巧写法(持续更新中~~~)
  • Webpack 4 学习01(基础配置)
  • Yeoman_Bower_Grunt
  • 番外篇1:在Windows环境下安装JDK
  • 关于Flux,Vuex,Redux的思考
  • 经典排序算法及其 Java 实现
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 蓝海存储开关机注意事项总结
  • 判断客户端类型,Android,iOS,PC
  • 如何进阶一名有竞争力的程序员?
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 一道闭包题引发的思考
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • FaaS 的简单实践
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​flutter 代码混淆
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #Ubuntu(修改root信息)
  • #在 README.md 中生成项目目录结构
  • $GOPATH/go.mod exists but should not goland
  • (3)nginx 配置(nginx.conf)
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (JS基础)String 类型
  • (Redis使用系列) Springboot 实现Redis消息的订阅与分布 四
  • (windows2012共享文件夹和防火墙设置
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (十一)手动添加用户和文件的特殊权限