【零基础学Python】09-Python装饰器的使用、反射的机制
🎯你正在阅读「Python 从零摸索日记」系列文章🎯
🔥 弹简特 个人主页
❄️个人专栏直通车:
- 💻软件测试入门记
- 🔌接口测试从入门到跑路
- ☕一个后端的 JavaEE 续命指南
- 🛜网络原理续命手册
✨靠热爱去书写自己,靠勇敢去书写生活!
🌟 博主简介:
文章目录:
- 一、装饰器
- 1.1 定义
- 1.2 装饰器的类型
- 1.3 装饰器的构造
- 1.4 装饰器的基本定义和使用
- 1.4.1 使用
- 1.4.2 一张图解释原理
- 1.5 装饰器的语法糖
- 1.6 装饰器使用的案例
- 二、反射的机制
- 2.1 反射的认识
- 2.2 反射的使用-添加或者覆盖(修改)
- 2.2.1 添加
- 2.2.1 覆盖(修改)
- 2.3 反射的使用-删除属性和方法
- 2.3.1 删除实例属性
- 2.3.2 删除类属性
- 2.3.3 删除实例方法
- 2.3.4 删除类方法
- 2.4 反射的使用-查询
- 2.5 反射读取属性或方法
- 2.5.1 使用
- 2.5.2 扩展技巧
一、装饰器
1.1 定义
核心一句话:
不改动原有函数的源代码、不修改原函数调用方式,就能给函数额外加新功能,遵循开闭原则(对外扩展开放、对内源码封闭,里面的代码你不能改),这就是装饰器。
生活化举例:
你有一杯白开水(原函数,原有功能:喝水),不想倒掉白开水、不改变“拿杯子喝水”的用法,直接往里面加柠檬/冰糖(新增功能),这个“加配料的操作”就等价于装饰器。
装饰器本质是高阶函数:
一个函数,如果能接收别的函数当参数,或者能返回一个新函数,它就叫高阶函数。
说白了:装饰器就是一个函数,它的“原料”是另一个函数。
1.2 装饰器的类型
- 函数的装饰器(常用)
- 类的装饰器
- 方法装饰器
1.3 装饰器的构造
- 装饰器本身是一个函数
- 装饰器的返回值一定是一个函数的引用(就是函数地址,这个地址存在函数名中)
- 装饰器有且只有一个固定的形参
- 装饰器定义的时候,括号里只能写1个形参,不能多不能少;
- 这个参数专门用来接住你要包装的那个原始函数地址。
例:def deco(原函数):这里就一个参数。
- 装饰器本质是闭包
- 装饰器里面会嵌套一个内层函数,内层函数能留住外面「原函数」这个变量,哪怕外层装饰器运行结束了,内层新函数依旧能调用原来的函数,这种嵌套留变量的结构就叫闭包,所以装饰器=闭包。
一句话理清楚:
装饰器就是一个函数,它会把你原本就写好、需要添加功能的那个函数拿过来,用自己的参数接住这个原函数,也就是接收原函数的地址,在内部给它包上一层新功能,最后把包装好的新函数地址返回给你,让你以后调用原函数时,实际运行的就是加了功能的新函数。
1.4 装饰器的基本定义和使用
1.4.1 使用
首先看一个没有被加强的函数
# 装饰器的使用defshopping():print('进入商城主页')print('开始购物')# 调用购物函数shopping()那么现在需求就是:我要对这个函数进行加强,就是在购物之前需要用户进行登录才可以,但是我不修改已经定义好的函数,此时我们就可以使用装饰器来完成这样的一个事情,如下:
deflogin(func):definner():print('请登录:')print('登录成功,即将跳到商城首页')# 对购物方法加强func()returninnerdefshopping():print('进入商城主页')print('开始购物')# 此时我们就调用装饰器函数,将shopping函数加强shopping_plus=login(shopping)shopping_plus()# 结果:# 请登录:# 登录成功,即将跳到商城首页# 进入商城主页# 开始购物1.4.2 一张图解释原理
1.5 装饰器的语法糖
我们上述使用装饰器的时候是需要自己去调用装饰器函数的,但是我们的代码步骤都是固定的,所以这个调用的过程我们有一个语法糖来为我们实现
语法:@装饰器函数
示例:
deflogin(func):definner():print('请登录:')print('登录成功,即将跳到商城首页')# 对购物方法加强func()returninner@logindefshopping():print('进入商城主页')print('开始购物')shopping()结果:
解释:
注意事项:装饰器函数必须定义在我们的被装饰函数的前面,否则会报错
# 装饰器函数必须定义在我们的被装饰函数的前面,否则会报错@login# 报错:name 'login' is not defineddefshopping():print('进入商城主页')print('开始购物')deflogin(func):definner():print('请登录:')print('登录成功,即将跳到商城首页')# 对购物方法加强func()returninner shopping()1.6 装饰器使用的案例
案例:通过装饰器来获取一个函数的执行时间
importtime# 使用装饰器假期我们的func函数:注意装饰器函数必须写在前面defout_func(func):definner():# 函数执行前的时间戳begin_time=time.time()# 执行函数func()# 函数执行之后的时间戳end_time=time.time()# 计算函数的执行时间print(f'函数的执行时间:{end_time-begin_time}')# 注意:最后不要忘记了还有返回值returninner# 被加强的函数@out_funcdeffunc():num_list=[]foriinrange(1,10000001):num_list.append(i)# 调用函数func()结果:
二、反射的机制
2.1 反射的认识
- 反射是基于面向对象类的封装和对象的创建的
- 使用反射,我们可以通过字符串类型对对象中具体某个属性或者方法使用
- 使用反射,我们可以实现对象的属性或者方法进行增删改查
解释
正常是直接写名字调用对象的属性/方法,反射反过来:拿着「字符串文字」就能找到、增删改对象里对应的属性或函数。
拆开通俗解释
- 普通用法:
obj.name,实打实写好属性名字name才能取值; - 反射:手里只有字符串
"name"这几个文字,照样能找到对象里叫name的属性,查值、改值、删属性、新加方法全都能做; - 底层依托类和对象的封装,Python靠
getattr/setattr/delattr/hasattr四个内置函数实现这个字符串匹配功能。
生活化举例
手机(对象)自带【打电话】功能,正常点开图标(直接写方法名phone.call()),反射就是只输入文字"call",系统凭这串文字自动打开打电话功能,还能新增APP、卸载APP、修改APP参数。
2.2 反射的使用-添加或者覆盖(修改)
2.2.1 添加
案例:给x这个对象,新增一个新的方法
前提:要有这个方法或者这个方法的引用,也就是必须有这个方法
语法:setattr(对象,方法名,函数引用)
- 方法名必须是字符串形式,以字符串参数传递
- 函数引用可以是自定义的函数地址,也可以内建函数的地址(地址存在我们的函数名中)
示例:内建函数
classA:defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 实例化一个对象x=A()# 需求:给这个x对象新增一个输入的方法(内建函数print)setattr(x,'e',print)x.e('我是x对象新增的e方法')结果:
示例:自定义函数
# 添加自定义函数classA:defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 自己定义一个函数deffunc(name):print(f'我是新增的函数,我的名字是{name}')# 实例化一个对象x=A()# 需求:给这个x对象新增一个自定义的方法setattr(x,'f',func)x.f('弹简特')结果:
2.2.1 覆盖(修改)
案例:x这个对象,覆盖这个对象原来的方法,使用新方法的功能
前提:要有这个新方法或者这个新方法的引用,也就是必须有这个新方法
语法:setattr(对象,对象中被覆盖的方法名,函数引用)
- 对象中被覆盖的方法名必须是字符串形式,以字符串参数传递
- 函数引用可以是自定义的函数地址,也可以内建函数的地址(地址存在我们的函数名中)
示例:
# x这个对象,覆盖这个对象原来的方法,使用新方法的功能# 添加自定义函数classA:defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 自己定义一个函数deffunc():print('我是覆盖a函数的新函数,我的名字是 李四')# 实例化一个对象x=A()print('覆盖之前:')x.a()# 结果:# 覆盖之前:# 调用实例方法a~print('---'*100)print('覆盖之后:')setattr(x,'a',func)x.a()# 结果:# 覆盖之后:# 我是覆盖a函数的新函数,我的名字是 李四结果:
2.3 反射的使用-删除属性和方法
2.3.1 删除实例属性
案例:给x这个对象,删除这个对象里面的money实例属性
语法:delattr(对象名,'实例属性名')
示例:
classA:def__init__(self,money):# 实例属性self.money=moneydefa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 实例化一个对象x=A(100)# 需求:删除x这个对象的实例属性money# 删除之前 可以调用实例属性print(x.money)# 开始删除实例属性delattr(x,'money')# 删除之后实例属性调用不了print(x.money)# 报错结果:AttributeError: 'A' object has no attribute 'money'2.3.2 删除类属性
案例:给x这个对象,删除这个对象里面的name类属性
语法:delattr(对象名,'类属性名')
示例:
classA:# 类属性name='李四'defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 实例化一个对象x=A()# 需求: 删除x这个对象里面的name类属性# 删除之前 可以调用类属性print(A.name)# 开始删除类属性delattr(x,'name')# 删除之后调用不了类属性print(A.name)# 报错结果:AttributeError: 'A' object has no attribute 'name'2.3.3 删除实例方法
案例:给x这个对象,删除这个对象里面的实例a方法
语法:delattr(对象名,'实例方法名')
示例:
# 删除实例方法classA:defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 实例化一个对象x=A()# 需求: 删除x这个对象里面的a实例方法# 删除之前 可以调用实例方法x.a()# 开始删除实例方法delattr(x,'a')# 删除之后调用不了实例方法x.a()# 报错结果:AttributeError: 'A' object has no attribute 'a'2.3.4 删除类方法
案例:给x这个对象,删除这个对象里面的累e方法
语法:delattr(对象名,'类方法名')
示例:
# 删除类方法classA:defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 定义一个类方法@classmethoddefe(cls):print('我是一个类方法')# 实例化一个对象x=A()# 需求: 删除x这个对象里面的e类方法# 删除之前 可以调用实例方法A.e()# 开始删除实例方法delattr(x,'e')# 删除之后调用不了实例方法A.e()# 报错结果:AttributeError: 'A' object has no attribute 'e'2.4 反射的使用-查询
案例:x这个对象,删除这个对象中是否有属性或者方法
语法:hasattr(对象名,'属性或者方法名')
返回值:hasattr()返回的是布尔类型
示例:
classA:# 实例属性name='tan'# 初始化实例属性的函数def__init__(self,money):self.money=money# 类方法@classmethoddefe(cls):print('我是类方法e~')# 实例方法defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')# 实例化一个对象x=A(200)# 查询是否有实例属性:有print(hasattr(x,'money'))# True# 查询是否有实例方法:有print(hasattr(x,'a'))# True# 查询是否有类属性:有print(hasattr(x,'name'))# True# 查询是否有类方法:有print(hasattr(x,'e'))# True# 查询是否有实例属性:没有print(hasattr(x,'money_no'))# False# 查询是否有实例方法:没有print(hasattr(x,'f'))# False# 查询是否有类属性:没有print(hasattr(x,'name_no'))# False# 查询是否有类方法:没有print(hasattr(x,'e_no'))# False2.5 反射读取属性或方法
2.5.1 使用
案例:可以使用getattr()反射把我们对象的属性或者方法传递给指定变量进行使用
语法:getattr(对象名,'属性或者方法名')
返回值:getattr()返回的是对应的属性值或者方法的地址值
示例:
classA:# 实例属性name='tan'# 初始化实例属性的函数def__init__(self,money):self.money=money# 类方法@classmethoddefe(cls):print('我是类方法e~')# 实例方法defa(self):print('调用实例方法a~')# 实例化一个对象x=A(200)# 实例属性m_i=getattr(x,'money')print(m_i)# 200# 实例方法a_i=getattr(x,'a')a_i()# 调用实例方法a~# 类属性name_c=getattr(x,'name')print(name_c)# tan# 类方法e_c=getattr(x,'e')e_c()# 我是类方法e~2.5.2 扩展技巧
扩展:将我们的所有方法的名字放到列表中,通过遍历列表反射提取掉用
示例:
classA:# 实例方法defa(self):print('调用实例方法a~')defb(self):print('调用实例方法b~')defc(self):print('调用实例方法c~')defd(self):print('调用实例方法d~')x=A()# 将我们的所有方法的名字放到列表中,通过遍历列表反射提取掉用foriin['a','b','c','d']:f=getattr(x,i)f()结果:
文章有用欢迎点赞收藏,专栏持续更新Python与软件测试干货,不足之处欢迎评论指正。
