【python入门到精通专题】8.装饰器
装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。
为什么需要装饰器
我们假设你的程序实现了say_hello()
和say_goodbye()
两个函数。
def say_hello():print("hello!")def say_goodbye():print("hello!") # 此处应打印goodbyeif __name__ == '__main__':say_hello()say_goodbye()
假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。
[DEBUG]: Enter say_hello()
Hello![DEBUG]: Enter say_goodbye()
Goodbye!
实现方式:
def say_hello():print("[DEBUG]: enter say_hello()")print("hello!") def say_goodbye():print("[DEBUG]: enter say_goodbye()")print("hello!")if __name__ == '__main__':say_hello()say_goodbye()
对上述代码进行优化:
def debug():import inspectcaller_name = inspect.stack()[1][3] # 可以返回函数名与当前函数的返回值print("[DEBUG]: enter {}()".format(caller_name))def say_hello():debug()print("hello!")def say_goodbye():debug()print("goodbye!")if __name__ == '__main__':say_hello()say_goodbye()
上述代码需要在每个业务函数里都要调用一下debug()
函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
如何实现一个装饰器
在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
def debug(func):def wrapper():print("[DEBUG]: enter {}()".format(func.__name__))return func()return wrapperdef say_hello():print("hello!")say_hello = debug(say_hello)say_hello()
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func):def wrapper():print("[DEBUG]: enter {}()".format(func.__name__))return func()return wrapper@debug
def say_hello():print("hello!")say_hello()
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper
接受和原函数一样的参数,比如:
def debug(func):def wrapper(something): # 指定一毛一样的参数print("[DEBUG]: enter {}()".format(func.__name__))return func(something)return wrapper # 返回包装过函数@debug
def say(something):print("hello {}!".format(something))say('顾安')
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了。
def debug(func):def wrapper(*args, **kwargs):print("[DEBUG]: enter {}()".format(func.__name__))return func(*args, **kwargs)return wrapper@debug
def say(something):print("hello {}!".format(something))say('顾安')
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level):def wrapper(func):def inner_wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=level,func=func.__name__))return func(*args, **kwargs)return inner_wrapperreturn wrapper@logging(level='INFO')
def say(something):print("say {}!".format(something))# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)@logging(level='DEBUG')
def do(something):print("do {}...".format(something))if __name__ == '__main__':say('hello')do("my work")
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG')
,它其实是一个函数,会马上被执行,只要它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
class Test:def __call__(self):print('call me!')t = Test()
t() # call me
像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class Logging:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))return self.func(*args, **kwargs)@Logging
def say(something):print("say {}!".format(something))say('木木')
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__
方法是就需要接收一个函数并返回一个函数。
class Logging:def __init__(self, level='INFO'):self.level = leveldef __call__(self, func): # 接收函数def wrapper(*args, **kwargs):print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))func(*args, **kwargs)return wrapper # 返回函数@Logging(level='INFO')
def say(something):print("say {}!".format(something))say('木木')
property
属性
@property
- 简介
什么是property属性?
一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。
class Foo:def func(self):pass# 定义property属性@propertydef prop(self):passfoo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用property属性
class Goods:@propertydef money(self):return 100goods = Goods()
print(goods.money)
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
- 调用时,无需括号
简单的示例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
class Pager:def __init__(self, current_page):# 用户当前请求的页码(第一页、第二页...)self.current_page = current_page# 每页默认显示10条数据self.per_items = 10@propertydef start(self):val = (self.current_page - 1) * self.per_itemsreturn val@propertydef end(self):val = self.current_page * self.per_itemsreturn val# ############### 调用 ###############
p = Pager(1)
print(p.start) # 就是起始值,即:m
print(p.end) # 就是结束值,即:n
- Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
- 装饰器 即:在方法上应用装饰器
- 类属性 即:在类中定义值为property对象的类属性
装饰器方式
在类的实例方法上应用@property装饰器
Python中的类有经典类
和新式类
,新式类
的属性比经典类
的属性丰富。( 如果类继object,那么该类是新式类 )
经典类,具有一种@property装饰器:
class Goods:@propertydef price(self):return 100obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)
新式类,具有三种@property装饰器
class Goods(object):@propertydef price(self):print('@property')@price.setterdef price(self, value):print('@price.setter')@price.deleterdef price(self):print('@price.deleter')obj = Goods()
print(obj.price) # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
注意:
- 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):def __init__(self):# 原价self.original_price = 100# 折扣self.discount = 0.8@propertydef price(self):# 实际价格 = 原价 * 折扣new_price = self.original_price * self.discountreturn new_price@price.setterdef price(self, value):self.original_price = value@price.deleterdef price(self):del self.original_priceobj = Goods()
print(obj.price) # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
# 当前属性被删除之后再获取则报错
# print(obj.price)
类属性方式,创建值为property对象的类属性
- 当使用类属性的方式创建property属性时,
经典类
和新式类
无区别
class Goods:def get_price(self):return 100price = property(get_price)obj = Goods()
result = obj.price # 自动调用get_price方法,并获取方法的返回值
print(result)
property方法中有个四个参数
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
class Foo(object):def get_bar(self):print("getter...")return 'a...'def set_bar(self, value):"""必须两个参数"""print("setter:", value)return 'set value' + valuedef del_bar(self):print("deleter...")return 'b...'BAR = property(get_bar, set_bar, del_bar, "description...")obj = Foo()print(obj.BAR) # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "c" # 自动调用第二个参数中定义的方法:set_bar方法,并将“c”当作参数传入
desc = Foo.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR # 自动调用第三个参数中定义的方法:del_bar方法
由于类属性方式
创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):def __init__(self):# 原价self.original_price = 100# 折扣self.discount = 0.8def get_price(self):# 实际价格 = 原价 * 折扣new_price = self.original_price * self.discountreturn new_pricedef set_price(self, value):self.original_price = valuedef del_price(self):del self.original_pricePRICE = property(get_price, set_price, del_price, '价格属性描述...')obj = Goods()
print(obj.PRICE) # 获取商品价格
obj.PRICE = 200 # 修改商品原价
print(obj.PRICE)
del obj.PRICE # 删除商品原价
@property
- 应用
- 私有属性添加getter和setter方法
class Money(object):def __init__(self):self.__money = 0def get_money(self):return self.__moneydef set_money(self, value):if isinstance(value, int):self.__money = valueelse:print("error:不是整型数字")money = Money()
print(money.get_money())
money.set_money(10)
print(money.get_money())
- 使用property升级getter和setter方法
class Money(object):def __init__(self):self.__money = 0def get_money(self):return self.__moneydef set_money(self, value):if isinstance(value, int):self.__money = valueelse:print("error:不是整型数字")# 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoneymoney = property(get_money, set_money)money_obj = Money()
money_obj.money = 100 # 调用setMoney方法
print(money_obj.money) # 调用getMoney方法
- 使用property取代getter和setter方法
- 重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):def __init__(self):self.__money = 0# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法@propertydef money(self):return self.__money# 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法@money.setterdef money(self, value):if isinstance(value, int):self.__money = valueelse:print("error:不是整型数字")money_obj = Money()
money_obj.money = 100
print(money_obj.money)
结语
以上就是关于python专题中的装饰器全部内容了,欢迎同学们在评论区讨论交流,有任何python开发、数据采集相关需求也可以后台或V加regentwan与我联系哟~
相关文章:

【python入门到精通专题】8.装饰器
装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello():print("hello!")def say_goodbye():print("hello!") # 此处应打印go…...

Halcon Blob分析提取小光斑
文章目录 算子complement 返回一个区域的补集select_region_point 选择包含指定像素的所有区域intensity 计算灰度值的均值和偏差 案例 算子 complement 返回一个区域的补集 complement(Region : RegionComplement : : )Region (输入对象):这指的是输入的一个或多…...

Lua
1.声明一个变量 只要赋值一个变量,就相当于新建了一个变量,默认全局变量,加一个local前缀之后,这个变量就变成了局部变量 a1//全局变量 local b2//局部变量2.nil类型 在Lua里没有被声明过的变量都是nil,nil是一种类…...

模型 总观效应
系列文章 分享 模型,了解更多👉 模型_思维模型目录。超越自我,洞见生命之渺小。 1 总观效应的呈现和应用 1.1 回首创业路,星辰大海的启示 陈浩是一名连续创业者,他的创业历程充满了起伏和挑战。在经历了几次失败后&a…...

【HarmonyOS NEXT】实现页面水印功能
关键词:鸿蒙、水印、Watermark、页面、触摸问题 注:本期文章同样适用 OpenHarmony 的开发 在app开发过程中时常会出现敏感信息页面,为保护信息安全和及时的数据追踪,通常会采用给页面加水印的形式,那么本期文章会介绍…...

selenium自动化测试之Junit
1. 常用的注解 将junit的索引添加到pom文件: <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId&…...

【氮化镓】基于氮化镓的互补逻辑集成电路[Nature Electronics]
【摘要】本文介绍了一种基于氮化镓(GaN)的互补金属氧化物半导体(CMOS)逻辑集成电路,该电路利用氧等离子体处理技术实现了增强型n沟道和p沟道GaN场效应晶体管的单片集成。研究者们展示了包括反相器、与非门、或非门和传输门在内的基本逻辑门,以及多级逻辑电路,如锁存器和…...

Linux之如何找回 root 密码?
1、启动系统,进入开界面,在界面中按“e"进入编辑界面 2、进入编辑界面,使用键盘上的上下键把光标往下移动,找到以”Linux16“开通内容所在的行数,在行的最后面输入:init/bin/sh 3、输入完成后&…...

后端参数校验方式
1. 使用Hibernate Validator进行注解校验 这是Java中最常用的参数校验方式,基于JSR 303/JSR 380规范的实现,通常结合Valid或Validated注解进行参数校验。 使用步骤: 添加依赖(如果使用Spring Boot,通常已经内置了Hi…...

访问控制列表(课内实验)
实验2:访问控制列表 实验目的及要求: 通过实验,进一步的理解标准ACL与扩展ACL的工作原理及执行过程。理解通配符的概念,熟练掌握标准ACL与扩展ACL的配置指令,掌握将访问控制列表应用VTY线路上,并且能够判断…...

处理Java内存溢出问题(java.lang.OutOfMemoryError):增加JVM堆内存与调优
处理Java内存溢出问题(java.lang.OutOfMemoryError):增加JVM堆内存与调优 在进行压力测试时,遇到java.lang.OutOfMemoryError: Java heap space错误或者nginx报错no live upstreams while connecting to upstream通常意味着应用的…...

数据分析库Pandas
一、认识Pandas数据分析库 Pandas是一个功能强大的数据分析库,它提供了丰富的数据结构和函数来处理和分析表格数据。在处理类似您提供的Excel文件时,首先需要导入Pandas库并读取数据,然后进行数据清洗和预处理,最后进行数据分析和…...

nginx做负载均衡的策略有哪些和模块
文章目录 策略模块 策略 轮询:轮询是Nginx默认的负载均衡策略,每个请求会按时间顺序分配到不同的后端服务器。这种方式适用于服务器配置相当且无状态的服务场景。加权轮询:在轮询的基础上,通过设置权重来调整不同服务器处理请求的…...

基于SSM社区医院预约转诊管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解
源代码数据库LW文档(1万字以上)开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统:Window操作系统 2、开发工具:IntelliJ IDEA或者Eclipse 3、数据库存储:…...

Android适配器更改,ListView未收到通知解析
只需要在每次适配器发生变化时,调用函数adapter.notifyDataSetChanged(); 其中的adapter是适配器名。 需要说明的适配器对应的数组:List<String>也是适配器的一部分。 如:以下代码中的lists数组 List<T> lists new ArrayLis…...

ubuntu18.04系统中图形化界面
一、Ubuntu 18.04 中,使用 GDM 作为默认的图形用户界面(GUI)管理器。GDM 是 GNOME Display Manager 的缩写,它是用于 Ubuntu 的显示管理器,负责处理登录和会话管理。 通过命令行重启 Ubuntu 18.04 上的图形界面服务&am…...

深入Semantic Kernel:插件开发与实践应用(进阶篇)
文章目录 一、引言二、开发Semantic Kernel插件三、实战3.1 时间信息插件3.2 小部件工厂插件3.3 初始化Semantic Kernel实例3.4 四个实战示例3.4.1 模型幻觉3.4.2 给模型提供时间信息3.4.3 AI自动调用函数3.4.4 AI自动调用和使用枚举 四、结论 一、引言 在上一篇入门文章《探索…...

基于SpringBoot+Vue+Uniapp的植物园管理小程序系统(2024最新,源码+文档+远程部署+讲解视频等)
3. 论文参考 4. 项目运行截图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而不是配置文件。Spring …...

2024zzuacm新生选拔赛第一场
2024zzuacm新生选拔赛第一场https://ac.nowcoder.com/acm/contest/92409 python代码源自我有异议症QAQ A - 降智视频 题意 起初有n个数都在丁丁手中,进行如下操作k次: 豆豆从丁丁手中拿走标号为奇数的数。对丁丁的其他的数进行重新标号。 问进行k次…...

IP地址如何支持远程办公?
由于当今社会经济的飞速发展,各个方向的业务都不免接触到跨省、跨市以及跨国办公的需要,随之而来的远程操作的不方便,加载缓慢,传输文件时间过长等困难,如何在万里之外实现远程办公呢?我们以以下几点进行阐…...

spring 集合注入格式
数组 List Set Map properties 案例 package org.example.dao.impl;import org.example.dao.BookDao;import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set;public class BookDaoImpl implements BookDao {private int[] array;p…...

基于Zabbix进行服务器运行情况监测
文章目录 引言I Zabbix主要构成下载并安装Zabbix被监控主机安装zabbix agent创建被监控主机报警设置II 常见问题cannot use database "zabbix": its "users" table is empty (is this the Zabbix proxy database?)重置 Zabbix Web 界面密码Zabbix agent i…...

Github优质项目推荐 - 第五期
文章目录 Github优质项目推荐 - 第五期一、【localsend】,47.5k stars - 附近设备文件互传二、【Pake】,29.9k stars - 网页变成桌面应用三、【laravel-crm】,10.7k stars - CRM 解决方案四、【localstack】,55.7k stars - 本地 A…...

Java_ EE (网络编程)
网络编程基本概念: 计算机网络计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。从其…...

Qt子线程运行报错error: undefined reference to `excelThread::conversionFinished()‘
如标题所示,笔者试图在子线程中使用基于GUI的对话框,而基于GUI的对话框需要在主线程(也称为GUI线程)中运行,在子线程中不能直接用,需要使用信号与槽机制,将请求发送到主线程,然后在主…...

VSCode 使用 EmmyLua 对lua进行调试
时间:2024年10月 其他:win10,EmmyLua v0.8.20 参考:https://blog.csdn.net/ShenHaoDeHao/article/details/140268354 有几个概念搞清楚就好理解了。一般开发中,我们编写的lua文件由宿主程序的来解析、执行࿱…...

neovim ubuntu中WARNING No clipboard tool found
我在vnc远程的ubuntu中做个临时开发,发现neovim无法复制文字,于是我:checkhealth查看了一下,测试结果如下: WARNING No clipboard tool found. Clipboard registers (" and "*) will not work.ADVICE::help clipboard …...

1882B - Sets and Union
题意就是有n个集合,对n个集合做并集得到 S S S, 现在抽n个集合中的集合做交集得到 T T T,问如何做让 S ≠ T S\neq T ST,并且让 T T T尽可能大。 这道题如果数据大一些做不了,但是数字大小只在50之间,并且集合的大小50,n大小50…...

thinkphp阿里云发送短信验证码,存储到缓存中完成手机号验证
源码demo下载地址 https://download.csdn.net/download/hanzhuhuaa/89865893 或者也可以在文章中查看demo源码 ↓ 第一步安装阿里云 SDK 您可以使用 Composer 来安装阿里云的 SDK。在项目目录中运行: composer require alibabacloud/sdk第二步发送短信 namespace app\api\c…...

题目解析:1423. 可获得的最大点数
题目解析:1423. 可获得的最大点数 > Problem: 1423. 可获得的最大点数 题目描述: 你有一个整数数组 cardPoints,表示排成一行的几张卡牌的点数。你每次可以从这排卡牌的 开头或末尾 拿一张卡牌,最终你需要正好拿 k 张卡牌。目…...