Python函数装饰器&异常处理
一、函数装饰器
1.1 函数装饰应用场景
你听说过开发和产品经理的故事吗?这两个物种聚在一起,充满了火药味。老产品经理对开发说,卧槽!你写的是什么??太low 你在执行前执行什么,执行后做什么。经常提需求,也经常改变需求。对于我们的开发,如果只有一个函数,我们可以改变它,但如果涉及100个函数,那么所有这100个函数都需要改变。
例如,支付功能涉及100个函数。今天,产品经理说,每个函数都需要添加一个功能来验证用户登录。然后我们必须一个接一个地打开和更改这100个函数!奇怪的是,他妈妈不得不加班!
def func1(): print("func1") def func2(): print("func2") def func3(): print("func3") """ 下面有一万个函数 """ func1() func2() func3()
现在产品经理过来拍了拍肩膀,攀登哇,我有一个简单的功能,你太棒了!它很快就完成了。您添加了1万个函数来验证用户功能。躺在槽里,我心里有1万匹草泥马,跑过去。加上白,我一个接一个地打开函数加起来,所以在我连续一个星期加班后,为他完成,如下:
def func1(): print("验证用户...") print("func1") def func2(): print("验证用户...") print("func2") def func3(): print("验证用户...") print("func3") """ 下面有一万个函数 """ func1() func2() func3()
上线后,产品经理又来了攀攀哇,这个验证功能,我想了想,用户体验不好,我们还是给他删吧!咦?你不想删吗?我今天的日报只是没有向老板汇报。我可以报告这个。 大家都这么说了,还能怎么办,硬着头皮改白,于是连续一周加班加点删掉。几天后,产品经理又来了,攀攀哇,我想了想,我修改了验证的逻辑,你再加上,这次一定行,好吧!又要连续一个星期通宵加班~~当然,如果我连续三周通宵加班,我可能会猝死!!
当然,这种情况很夸张,不要惊慌。但在实际开发中,这种频繁的修改需求更为常见。如何有效地解决这个问题,然后反映了函数装饰的作用。
1.2 函数装饰器的写法
函数装饰本质上是一种函数。
def outer(fun): span class="token keyword">def wrapper():
print("支付之前:验证用户....")
fun()
print("支付之后: 谢谢老板....")
return wrapper
@outer
def func1():
print("执行:func1")
@outer
def func2():
print("执行:func2")
@outer
def func3():
print("执行:func3")
""" 下面有一万个函数 """
func1()
func2()
func3()
@outer这个注解,是让func1(),func2(),func3() 这些函数,与我们新写的outer()函数勾搭起来。在这里面outer()函数,就是func1,func2,func3 这些函数的装饰器。
首先要注意,装饰器实际上是一个函数。
-
在需要调用的函数上面加上@装饰器函数名,比如@outer ,让装饰器和函数勾搭起来(建立连接)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SbL1PNiY-1639397096253)(补充:函数装饰器&异常处理.assets/image-20211118155922983.png)]
-
将调用的函数,绑定到装饰器函数的参数里面,比如上面的案例中,outer(fun) ,实际上fun = func1()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjcWTXgs-1639397096253)(补充:函数装饰器&异常处理.assets/image-20211118160013671.png)]
-
加载装饰器函数中的内部函数wrapper()函数,并返回wrapper函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEjInKKt-1639397096254)(补充:函数装饰器&异常处理.assets/image-20211118160113511.png)]
-
此时原本调用的函数,会被返回的wrapper()函数替代。比如上面调用func1()最后会被修改成wrapper()函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FQ4lF4KG-1639397096254)(补充:函数装饰器&异常处理.assets/image-20211118160152619.png)]
-
执行装饰器中的wrapper函数
1.4 带参数函数的装饰器
def outer(fun):
def wrapper(name):
print("支付之前:验证用户....")
fun(name)
print("支付之后: 谢谢老板....")
return wrapper
def func1(arg):
print("func1....",arg)
func1("攀哥...")
1.5 带返回值函数的装饰器
def outer(fun):
def wrapper(name):
print("支付之前:验证用户....")
result = fun(name)
print("支付之后: 谢谢老板....")
return result
return wrapper
def func1(name):
print("func1....", name)
return "%s真帅" % name
result = func1("攀哥...")
print(result)
1.6 作业:
1. 编写一个统计一个字符串中有多少个字符,和每一个字符出现多少次的函数
2. 为这个函数编写一个装饰器,用来计算执行这个函数需要多长时间
3. 提示: 获取系统当前的毫秒数,导入timer模块 然后使用:timer.timer()
二,异常处理
2.1 什么是异常
什么叫异常?超出逾期的。比如:
在开发过程中,切记,一定不要让用户看到系统出错的信息。
2.2 常见的异常
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
2.3 异常的使用场景
比如,在除法运算的时候,我们知道除数不能为0.那么在程序中如果除数为0了,就会出现异常
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
print(num1)
print(num2)
请输入第一个数:40
请输入第二个数:0
Traceback (most recent call last):
File "D:\py_work\study\basic\异常.py", line 4, in <module>
print(num1/num2)
ZeroDivisionError: division by zero
Process finished with exit code 1
我们会发现,当num2为0的时候,就会出现ZeroDivisionError: division by zero 异常信息。并且程序这个时候会终端,也就是后面的两行打印语句没有打印。
在开发中,为了用户的体验,避免用户看到爆红的错误信息,同时也为了系统的安全性,我们会屏蔽掉这个错误信息。这个时候我们需要捕捉并处理一下异常。
2.4 捕捉和处理异常的方式
捕捉异常可以使用 try/except语句。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
如果你不想在异常发生时结束你的程序,只需在try里捕获它。
格式:
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
看如下代码:
try:
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
except ZeroDivisionError:
print("哥们儿!除数不为零?懂?")
else:
print("nice!我马上为你计算")
print(num1)
print(num2)
运行效果:
请输入第一个数:40
请输入第二个数:0
哥们儿!除数不为零?懂?
40
0
上面代码中,except ZeroDivisionError 表示捕捉除数为0的异常,如果num2=0,就会出现除数为0的异常,会被except捕捉到。然后执行except里面的代码。
如果num2不为0,就没有异常,这个时候可以执行else中的代码。
注意:else 不是必须的。可以不写!!
try 语句按照如下方式工作;
- 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
- 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
- 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
- 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。
2.5 使用except而不带任何异常类型
try:
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
except :
print("哥们儿!除数不为零?懂?")
else:
print("nice!我马上为你计算")
print(num1)
print(num2)
以上方式try-except语句捕获所有发生的异常。但这不是一个很好的方式,我们不能通过该程序识别出具体的异常信息。因为它捕获所有的异常。
2.6 如何同时捕获多个异常
格式1:
try:
....
except 异常1:
....
except 异常2:
....
else:
....
wife_list = ["赵丽颖","张天爱","宵鱼鱼","戴雨彤"]
try:
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
for x in range(len(wife_list)):
print(x,".",wife_list[x])
index = int(input("请输入今晚要翻牌的编号"))
wife = wife_list[index]
print("你今天选的是:",wife)
except ZeroDivisionError:
print("哥们儿!除数不为零?懂?")
except IndexError:
print("哥们儿!醒醒你没有这个老婆")
else:
print("nice!我马上为你安排")
格式2:
try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]):
发生以上多个异常中的一个,执行这块代码
......................
else:
如果没有异常执行这块代码
例如:
wife_list = ["赵丽颖","张天爱","宵鱼鱼","戴雨彤"]
try:
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
for x in range(len(wife_list)):
print(x,".",wife_list[x])
index = int(input("请输入今晚要翻牌的编号"))
wife = wife_list[index]
print("你今天选的是:",wife)
except (ZeroDivisionError,IndexError):
print("数据输入有误~~")
else:
print("这就给你安排")
当num2=0的时候,会发生除数为零的异常,会被except ZeroDivisionError: 捕获。如果选取了wife_list列表不存在的索引时,就会被except IndexError:捕获。如果没有发生异常,就会执行else中的语句
2.7 try-finally 语句
try-finally 语句无论是否发生异常都将执行最后的代码。
wife_list = ["赵丽颖","张天爱","宵鱼鱼","戴雨彤"]
try:
num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))
print(num1/num2)
for x in range(len(wife_list)):
print(x,".",wife_list[x])
index = int(input("请输入今晚要翻牌的编号"))
wife = wife_list[index]
print("你今天选的是:",wife)
except ZeroDivisionError:
print("哥们儿!除数不为零?懂?")
except IndexError:
print("哥们儿!醒醒你没有这个老婆")
else:
print("这就给你安排")
finally:
print("龙仔竭诚为你服务")
2.8 打印异常信息
一个异常可以带上参数,可作为输出的异常信息参数。
你可以通过except语句来捕获异常的参数,如下所示:
try:
正常的操作
......................
except ExceptionType as Argument:
你可以在这输出 Argument 的值...
比如:
while True:
try:
x = int(input("请输入一个数字: "))
break
except ValueError as e:
print(e)
print("您输入的不是数字,请再次尝试输入!")
2.9 抛出异常
Python 使用 raise 语句主动抛出一个指定的异常。
raise语法格式如下:
raise Exception("异常信息")
比如:我们一般在注册账号的时候要求密码必须是6-18位,如果用户输入错误,我们需要抛出异常
while True:
str = input("请输入6-18位密码")
if len(str)<6 or len(str)>18:
raise Exception("密码必须时6-18位")