2023年11月29日发(作者:)
Python官⽅中⽂教程(转)6
错误异常
Python官⽅中⽂教程(转) 6
转⾃
1. 什么是异常?
在程序运⾏过程中,总会遇到各种各样的问题和错误。
有些错误是我们编写代码时⾃⼰造成的,⽐如语法错误、调⽤错误,甚⾄逻辑错误。下⾯这个例⼦,在输⼊后输⼊了,没有按照
if回车
Python 的语法规则来,所以直接抛出了语法错误。
>>> if
File "
if
^
SyntaxError: invalid syntax
还有⼀些错误,则是不可预料的错误,但是完全有可能发⽣的,⽐如⽂件不存在、磁盘空间不⾜、⽹络堵塞、系统错误等等。下⾯这个例
⼦,使⽤ 函数打开 ⽂件,可是在当前⽬录下并没有这个⽂件,所以⼀定会打开失败,抛出了。
openIOError
>>> fp = open('')
Traceback (most recent call last):
File "
IOError: [Errno 2] No such file or directory: ''
这些导致程序在运⾏过程中出现异常中断和退出的错误,我们统称为异常。正常情况下,异常都不会被程序处理,⽽是以错误信息的形式展
现出来。
异常有很多种类型,Python内置了⼏⼗种常见的异常,就在模块内,它们⽆需特别导⼊,就可以直接使⽤。需要注意的是,所有的
builtins
异常都是异常类,⾸字母是⼤写的!
在发⽣异常的时候,Python会打印出异常信息,信息的前⾯部分显⽰了异常发⽣的上下⽂环境,并以调⽤栈的形式显⽰具体信息。异常类
型作为信息的⼀部分也会被打印出来,例如,。
ZeroDivisionErrorTypeError
>>> 1/0
Traceback (most recent call last):
File "
ZeroDivisionError: integer division or modulo by zero
>>>
>>>
>>> 10 + "1"
Traceback (most recent call last):
File "
TypeError: unsupported operand type(s) for +: 'int' and 'str'
正常情况下,我们都不需要去记住 Python 到底内置了哪些错误和异常类型,除⾮你需要去捕获它,关于捕获的内容,我会放在下⼀节。这
⼀节先来认识⼀下 Python 中有哪些常见的错误和异常,对于新⼿,下⾯的内容⼤概过⼀下就好,不⽤深究,因为这些在你以后的编码中都
会遇到的。
1.1 SyntaxError
SyntaxError
,是语法错误,可能是新⼿在学习 Python 时最容易遇到的错误
>>> while True print('Hello world')
File "
while True print('Hello world')
^
SyntaxError: invalid syntax
1.2 TypeError
TypeError
,是类型错误,也就是说将某个操作或功能应⽤于不合适类型的对象时引发,⽐如整型与字符型进⾏加减法
>>> a = 10
>>> b = "1"
>>>
>>> a-b
Traceback (most recent call last):
File "
TypeError: unsupported operand type(s) for -: 'int' and 'str'
1.3 IndexError
IndexError
,是指索引出现了错误,⽐如最常见下标索引超出了序列边界
>>> alist = [0,1,2]
>>> alist[5]
Traceback (most recent call last):
File "
IndexError: list index out of range
1.4 KeyError
KeyError
是关键字错误,这个异常主要发⽣在字典中,⽐如当⽤户试图访问⼀个字典中不存在的键时会被引发。
>>> profile = {"name": "王炳明"}
>>> profile["age"]
Traceback (most recent call last):
File "
KeyError: 'age'
1.5 ValueError
ValueError
为值错误,当⽤户传⼊⼀个调⽤者不期望的值时会引发,即使这个值的类型是正确的,⽐如想获取⼀个列表中某个不存在值的索
引。
>>> int("1")
1
>>> int("a")
Traceback (most recent call last):
File "
ValueError: invalid literal for int() with base 10: 'a'
1.6 AttributeError
AttributeError
是属性错误,当⽤户试图访问⼀个对象不存在的属性时会引发。
⽐如字典有⽅法,⽽列表却没有,所以对⼀个列表对象调⽤该⽅法就会引发该异常。
get
>>> alist = [0,1,2]
>>> alist.get(0)
Traceback (most recent call last):
File "
AttributeError: 'list' object has no attribute 'get'
1.7 NameError
NameError
是指变量名称发⽣错误,⽐如⽤户试图调⽤⼀个还未被赋值或初始化的变量时会被触发。
>>> name
Traceback (most recent call last):
File "
NameError: name 'name' is not defined
1.8 IOError
IOError
为打开⽂件错误,当⽤户试图以读取⽅式打开⼀个不存在的⽂件时引发。
>>> fb = open('')
Traceback (most recent call last):
File "
IOError: [Errno 2] No such file or directory: ''
1.9 StopIteration
StopIteration
为迭代器错误,当访问⾄迭代器最后⼀个值时仍然继续访问,就会引发这种异常,提醒⽤户迭代器中已经没有值可供访问了。
>>> alist = range(2)
>>> agen = iter(alist)
>>> next(agen)
0
>>> next(agen)
1
>>> next(agen)
Traceback (most recent call last):
File "
StopIteration
1.10 AssertionError
AssertionError
为断⾔错误,当⽤户利⽤断⾔语句检测异常时,如果断⾔语句检测的表达式为假,则会引发这种异常。
>>> alist = [0,1,2]
>>> assert isinstance(alist, list)
>>> assert isinstance(alist, dict)
Traceback (most recent call last):
File "
AssertionError
1.11 IndentationError
Python 是⼀门严格缩进的语⾔,如果缩进有问题,就会导致解释器解析异常,抛出
IndentationError
>>> while True:
... print("hello")
File "
print("hello")
^
IndentationError: expected an indented block
1.12 ImportError
当你在使⽤ 导包的时候,如果因为包名错误或者路径不对、包未安装,都会抛出
importImportError
>>> import oxx
Traceback (most recent call last):
File "
ImportError: No module named oxx
2. 如何抛出和捕获异常
2.1 如何抛出异常?
异常的产⽣有两种来源:
⼀种是程序⾃动抛出,⽐如/会⾃动抛出
10ZeroDivisionError
⼀种是开发者主动抛出,使⽤关键字抛出。
raise
在 Python 中是使⽤ 关键字来抛出异常的,⽐如在下⾯这个函数中,如果不存在⽬标⽂件,则会抛出⼀个 通⽤异常。
raiseException
def demo_func(filename):
if not os.path.isfile(filename):
raise Exception
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">
2.2 如何捕获异常?
出现错误或者异常没有关系,关键在于你要学会预判程序可能会出现的错误或异常,然后在代码中捕获这些异常并处理。
异常的捕获的语法有如下四种:
第⼀种语法
只捕捉但是不想获取异常信息
try:
代码A
except [EXCEPTION]:
代码B
第⼆种语法
不但捕捉了还要获取异常信息,赋值给 后,后⾯你可以把异常信息打印到⽇志中。
e
try:
代码A
except [EXCEPTION] as e:
代码B
有了上⾯的基础语法,可以扩展出下⾯三种常⽤的异常捕获的写法。
第三种语法
正常使⽤
try ... except ...
如果代码发⽣了异常,则会⾛到代码的逻辑。
AB
try:
代码A
except [exception] as e :
代码B
举个例⼦
>>> try:
... 1/0
... except ZeroDivisionError as e:
... print("发⽣了异常:错误信息如下: n" + str(e))
...
发⽣了异常:错误信息如下:
integer division or modulo by zero
第四种语法
使⽤
try ... except ... else
如果代码发⽣了异常,则会⾛到代码的逻辑,如果没有发⽣异常,则会⾛到代码
ABC
try:
代码A
except [exception] as e:
代码B
else:
代码C
举个例⼦
不发⽣异常的情况
>>> try:
... 4/2
... except ZeroDivisionError as e:
... print("发⽣了异常:错误信息如下: n" + str(e))
... else:
... print("程序正常运⾏")
...
2
程序正常运⾏
发⽣异常的情况
>>> try:
... 1/0
... except ZeroDivisionError as e:
... print("发⽣了异常:错误信息如下: n" + str(e))
... else:
... print("程序正常运⾏")
...
发⽣了异常:错误信息如下:
integer division or modulo by zero
第三种:使⽤
try ... except ... finally
举个例⼦
发⽣异常的情况
>>> try:
... 1/0
... except ZeroDivisionError as e:
... print("发⽣了异常:错误信息如下: n" + str(e))
... finally:
... print("程序运⾏结束!!")
...
发⽣了异常:错误信息如下:
integer division or modulo by zero
程序运⾏结束!!
不发⽣异常的情况
>>> try:
... 4/2
... except ZeroDivisionError as e:
... print("发⽣了异常:错误信息如下: n" + str(e))
... finally:
... print("程序运⾏结束!!")
...
2
程序运⾏结束!!
2.3 捕获多个异常?
每个捕获⼀个异常
except
⼀个 语句可能有多个 ⼦句,以指定不同异常的处理程序,但是最多会执⾏⼀个处理程序。
tryexcept
当代码 在运⾏中抛出了异常时,Python 解释器会逐⾏运⾏代码,如果抛出的异常是 那么后⾯直接运⾏代码,运⾏完
Aexception1B
Bexcept
后,就不会再判断后⾯两个语句了。
⽽如果不是,⽽是 ,那会运⾏代码,⽽不会再运⾏第三个语句了。
exception1exception2Cexcept
try:
代码A
except [exception1] as e:
代码B
except [exception2] as e:
代码C
except [exception3] as e:
代码D
举个例⼦吧,下⾯这段代码,由于 /会抛出 错误,所以前⾯两个异常匹配都不成功,⽽在最后⼀个成功匹配
10ZeroDivisionErrorexcept
上,最终打印出 除数不能为
0
try:
1/0
except IOError:
print("IO读写出错")
except FloatingPointError:
#
浮点计算错误
print("计算错误")
except ZeroDivisionError:
# 0
除数不能为
print("计算错误")
# output:
计算错误
⼀个捕获多个异常
except
上⾯的例⼦可以看出来,第⼆个异常和第三个异常是属于同⼀类,就是 计算错误,异常处理的代码是⼀样的,那有没有办法将它们合并在
⼀起呢,简化⼀下代码呢?
答案是,可以的。
在后⾯其实是可以接多个异常的,多个异常之间使⽤括号包裹。只要匹配上⼀个就算捕获到,就会进⼊相应的代码分⽀。
except
try:
1/0
except IOError:
print("IO读写出错")
except (ZeroDivisionError, FloatingPointError):
print("计算出错")
# output:
计算错误
3. 如何⾃定义异常
⼤多数情况下,内置的错误和异常已经够⽤了,但是有时候你还是需要⾃定义⼀些异常。
⾃定义异常,需要你对 类 与 继承 有⼀些了解,对于类的知识,我放在了第七章,因此你可以先前往学习下第七章的的下⾯两节内容:
等学习完后再回过头来学习本节内容。
⾃定义异常应该继承 类,直接继承或者间接继承都可以,⾃定义的异常或错误类,下⾯使⽤ ,表⽰接受⽤户输⼊时发
ExceptionInputError
⽣问题。
class InputError(Exception):
def __init__(self, msg):
self.message = msg
def __str__(self):
return self.message
异常的名字都以结尾,我们在为⾃定义异常命名的时候也需要遵守这⼀规范,就跟标准的异常命名⼀样。
Error
定义完后,再看如下代码,我在 ⾥调⽤ 函数,如果发现⽤户没有输⼊内容,就使⽤ 关键字来抛出 。
tryget_inputraiseInputError
def get_input():
name = input("请输⼊你的姓名:")
if name == "":
raise InputError("未输⼊内容")
try:
get_input()
except InputError as e:
print(e)
4. 【进阶】如何关闭异常⾃动关联上下⽂
当你在处理异常时,由于处理不当或者其他问题,再次抛出另⼀个异常时,往外抛出的异常也会携带原始的异常信息。
就像这样⼦。
try:
print(1 / 0)
except Exception as exc:
raise RuntimeError("Something bad happened")
从输出可以看到两个异常信息
Traceback (most recent call last):
File "", line 2, in <module>
print(1 / 0)
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "", line 4, in <module>
raise RuntimeError("Something bad happened")
RuntimeError: Something bad happened
如果在异常处理程序或块中引发异常,默认情况下,异常机制会隐式⼯作会将先前的异常附加为新异常的属性。这就是
finally__context__
Python 默认开启的⾃动关联异常上下⽂。
如果你想⾃⼰控制这个上下⽂,可以加个 关键字(from 语法会有个限制,就是第⼆个表达式必须是另⼀个异常类或实例。),来表
from
明你的新异常是直接由哪个异常引起的。
try:
print(1/0)
except Exception as exc:
raise RuntimeError("Something bad happened") from exc
输出如下
Traceback (most recent call last):
File "", line 2, in <module>
print(1 / 0)
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "", line 4, in <module>
raise RuntimeError("Something bad happened") from exc
RuntimeError: Something bad happened
当然,你也可以通过⽅法为异常设置上下⽂属性,这也能在更好的显⽰异常信息。
with_traceback()__context__traceback
try:
print(1 / 0)
except Exception as exc:
raise RuntimeError("bad thing").with_traceback(exc)
最后,如果我想彻底关闭这个⾃动关联异常上下⽂的机制?有什么办法呢?
可以使⽤ ,从下⾯的例⼦上看,已经没有了原始异常
from None
$ cat demo.py
try:
print(1 / 0)
except Exception as exc:
raise RuntimeError("Something bad happened") from None
$
$ python demo.py
Traceback (most recent call last):
File "", line 4, in <module>
raise RuntimeError("Something bad happened") from None
RuntimeError: Something bad happened
(PythonCodingTime)
5. 【进阶】异常处理的三个好习惯
如果你⽤ Python 编程,那么你就⽆法避开异常,因为异常在这门语⾔⾥⽆处不在。打个⽐⽅,当你在脚本执⾏时按 退出,解释器就
ctrl+c
会产⽣⼀个 异常。⽽ 、、等更是⽇常编程⾥随处可见的⽼朋友。
KeyboardInterruptKeyErrorValueErrorTypeError
异常处理⼯作由“捕获”和“抛出”两部分组成。“捕获”指的是使⽤包裹特定语句,妥当的完成错误流程处理。⽽恰当的使
try ... except
⽤主动“抛出”异常,更是优雅代码⾥必不可少的组成部分。
raise
在这篇⽂章⾥,我会分享与异常处理相关的 3 个好习惯。继续阅读前,我希望你已经了解了下⾯这些知识点:
5.1 只做最精确的异常捕获
假如你不够了解异常机制,就难免会对它有⼀种天然恐惧感。你可能会觉得:异常是⼀种不好的东西,好的程序就应该捕获所有的异常,让
⼀切都平平稳稳的运⾏。⽽抱着这种想法写出的代码,⾥⾯通常会出现⼤段含糊的异常捕获逻辑。
让我们⽤⼀段可执⾏脚本作为样例:
# -*- coding: utf-8 -*-
import requests
import re
def save_website_title(url, filename):
"""获取某个地址的⽹页标题,然后将其写⼊到⽂件中
:returns: 如果成功保存,返回 True,否则打印错误,返回 False
"""
try:
resp = requests.get(url)
obj = re.search(r'
if not obj:
print('save failed: title tag not found in page content')
return False
title = obj.grop(1)
with open(filename, 'w') as fp:
fp.write(title)
return True
except Exception:
print(f'save failed: unable to save title of {url} to {filename}')
return False
def main():
save_website_title('', 'qq_')
if __name__ == '__main__':
main()
脚本⾥的 函数做了好⼏件事情。它⾸先通过⽹络获取⽹页内容,然后利⽤匹配出标题,最后将标题写在本地⽂件
save_website_title正则
⾥。⽽这⾥有两个步骤很容易出错:与 。所以在代码⾥,我们⽤⼀个⼤⼤的 语句块,将这⼏个步骤都包
⽹络请求本地⽂件操作try ... except
裹了起来。安全第⼀


发布评论