2023年11月29日发(作者:)

Python官⽅中⽂教程(转)6

错误异常

Python官⽅中⽂教程(转) 6

转⾃

1. 什么是异常?

在程序运⾏过程中,总会遇到各种各样的问题和错误。

有些错误是我们编写代码时⾃⼰造成的,⽐如语法错误、调⽤错误,甚⾄逻辑错误。下⾯这个例⼦,在输⼊后输⼊了,没有按照

if回车

Python 的语法规则来,所以直接抛出了语法错误。

>>> if

File "", line 1

if

^

SyntaxError: invalid syntax

还有⼀些错误,则是不可预料的错误,但是完全有可能发⽣的,⽐如⽂件不存在、磁盘空间不⾜、⽹络堵塞、系统错误等等。下⾯这个例

⼦,使⽤ 函数打开 ⽂件,可是在当前⽬录下并没有这个⽂件,所以⼀定会打开失败,抛出了

openIOError

>>> fp = open('')

Traceback (most recent call last):

File "", line 1, in <module>

IOError: [Errno 2] No such file or directory: ''

这些导致程序在运⾏过程中出现异常中断和退出的错误,我们统称为异常。正常情况下,异常都不会被程序处理,⽽是以错误信息的形式展

现出来。

异常有很多种类型,Python内置了⼏⼗种常见的异常,就在模块内,它们⽆需特别导⼊,就可以直接使⽤。需要注意的是,所有的

builtins

异常都是异常类,⾸字母是⼤写的!

在发⽣异常的时候,Python会打印出异常信息,信息的前⾯部分显⽰了异常发⽣的上下⽂环境,并以调⽤栈的形式显⽰具体信息。异常类

型作为信息的⼀部分也会被打印出来,例如

ZeroDivisionErrorTypeError

>>> 1/0

Traceback (most recent call last):

File "", line 1, in <module>

ZeroDivisionError: integer division or modulo by zero

>>>

>>>

>>> 10 + "1"

Traceback (most recent call last):

File "", line 1, in <module>

TypeError: unsupported operand type(s) for +: 'int' and 'str'

正常情况下,我们都不需要去记住 Python 到底内置了哪些错误和异常类型,除⾮你需要去捕获它,关于捕获的内容,我会放在下⼀节。这

⼀节先来认识⼀下 Python 中有哪些常见的错误和异常,对于新⼿,下⾯的内容⼤概过⼀下就好,不⽤深究,因为这些在你以后的编码中都

会遇到的。

1.1 SyntaxError

SyntaxError

,是语法错误,可能是新⼿在学习 Python 时最容易遇到的错误

>>> while True print('Hello world')

File "", line 1

while True print('Hello world')

^

SyntaxError: invalid syntax

1.2 TypeError

TypeError

,是类型错误,也就是说将某个操作或功能应⽤于不合适类型的对象时引发,⽐如整型与字符型进⾏加减法

>>> a = 10

>>> b = "1"

>>>

>>> a-b

Traceback (most recent call last):

File "", line 1, in <module>

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 "", line 1, in <module>

IndexError: list index out of range

1.4 KeyError

KeyError

是关键字错误,这个异常主要发⽣在字典中,⽐如当⽤户试图访问⼀个字典中不存在的键时会被引发。

>>> profile = {"name": "王炳明"}

>>> profile["age"]

Traceback (most recent call last):

File "", line 1, in <module>

KeyError: 'age'

1.5 ValueError

ValueError

为值错误,当⽤户传⼊⼀个调⽤者不期望的值时会引发,即使这个值的类型是正确的,⽐如想获取⼀个列表中某个不存在值的索

引。

>>> int("1")

1

>>> int("a")

Traceback (most recent call last):

File "", line 1, in <module>

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 "", line 1, in <module>

AttributeError: 'list' object has no attribute 'get'

1.7 NameError

NameError

是指变量名称发⽣错误,⽐如⽤户试图调⽤⼀个还未被赋值或初始化的变量时会被触发。

>>> name

Traceback (most recent call last):

File "", line 1, in <module>

NameError: name 'name' is not defined

1.8 IOError

IOError

为打开⽂件错误,当⽤户试图以读取⽅式打开⼀个不存在的⽂件时引发。

>>> fb = open('')

Traceback (most recent call last):

File "", line 1, in <module>

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 "", line 1, in <module>

StopIteration

1.10 AssertionError

AssertionError

为断⾔错误,当⽤户利⽤断⾔语句检测异常时,如果断⾔语句检测的表达式为假,则会引发这种异常。

>>> alist = [0,1,2]

>>> assert isinstance(alist, list)

>>> assert isinstance(alist, dict)

Traceback (most recent call last):

File "", line 1, in <module>

AssertionError

1.11 IndentationError

Python 是⼀门严格缩进的语⾔,如果缩进有问题,就会导致解释器解析异常,抛出

IndentationError

>>> while True:

... print("hello")

File "", line 2

print("hello")

^

IndentationError: expected an indented block

1.12 ImportError

当你在使⽤ 导包的时候,如果因为包名错误或者路径不对、包未安装,都会抛出

importImportError

>>> import oxx

Traceback (most recent call last):

File "", line 1, in <module>

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'(.*)', resp.text)

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

裹了起来。安全第⼀