Python 3-11 异常处理

异常处理

一、错误和异常

1、句法错误

句法错误又称解析错误:

>>> while True print('Hello world')
  File "<stdin>", line 1
    while True print('Hello world')
               ^
SyntaxError: invalid syntax

解析器会复现出现句法错误的代码行,并用小“箭头”指向行里检测到的第一个错误。错误是由箭头 上方 的 token 触发的(至少是在这里检测出的):本例中,在 print() 函数中检测到错误,因为,在它前面缺少冒号(':') 。错误信息还输出文件名与行号,在使用脚本文件时,就可以知道去哪里查错。

 

2、异常

即使语句或表达式使用了正确的语法,执行时仍可能触发错误。执行时检测到的错误称为 异常。大多数异常不会被程序处理,而是显示下列错误信息:

>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> n*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

错误信息的最后一行说明程序遇到了什么类型的错误。异常有不同的类型,而类型名称会作为错误信息的一部分中打印出来:上述示例中的异常类型依次是:ZeroDivisionError, NameError 和 TypeError。作为异常类型打印的字符串是发生的内置异常的名称。对于所有内置异常都是如此,但对于用户定义的异常则不一定如此。标准的异常类型是内置的标识符(不是保留关键字)。

此行其余部分根据异常类型,结合出错原因,说明错误细节。

错误信息开头用堆栈回溯形式展示发生异常的语境。 一般会列出源代码行的堆栈回溯;但不会显示从标准输入读取的行。

内置异常 列出了内置异常及其含义。

3、处理异常

可以编写程序处理选定的异常。下例会要求用户一直输入内容,直到输入有效的整数,但允许用户中断程序(Ctrl+C);注意,用户中断程序会触发 KeyboardInterrupt 异常。

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops!  That was no valid number.  Try again...")

try 语句的工作原理如下:

  • 首先,执行 try 子句 (try 和 except 关键字之间的语句块)。
  • 如果没有异常发生,则跳过 except 子句 并完成 try 语句的执行。
  • 如果在执行 try 子句时发生了异常,则跳过该子句中剩下的部分。 然后,如果异常的类型和 except 关键字后面的异常匹配,则执行 except 子句,然后继续执行 try 语句之后的代码。
  • 如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个 未处理异常,执行将停止并显示如上所示的消息。

一个 try 语句可能有多个 except 子句,以指定不同异常的处理程序。 最多会执行一个处理程序。 处理程序只处理相应的 try 子句中发生的异常,而不处理同一 try 语句内其他处理程序中的异常。 一个 except 子句可以将多个异常命名为带括号的元组,例如:

except (RuntimeError, TypeError, NameError):
    pass

如果发生的异常和 except 子句中的类是同一个类或者是它的基类,则异常和 except 子句中的类是兼容的(但反过来则不成立 --- 列出派生类的 except 子句与基类不兼容)。 例如,下面的代码将依次打印 B, C, D

class B(Exception):pass

class C(B):pass

class D(C):pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

请注意如果 except 子句被颠倒(把 except B 放到第一个),它将打印 B,B,B --- 即第一个匹配的 except 子句被触发。

最后的 except 子句可以省略异常名,以用作通配符。但请谨慎使用,因为以这种方式很容易掩盖真正的编程错误!它还可用于打印错误消息,然后重新引发异常(同样允许调用者处理异常):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

try ... except 语句有一个可选的 else 子句,在使用时必须放在所有的 except 子句后面。对于在 try 子句不引发异常时必须执行的代码来说很有用。 例如:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

使用 else 子句比向 try 子句添加额外的代码要好,因为它避免了意外捕获非 try ... except 语句保护的代码引发的异常。

发生异常时,它可能具有关联值,也称为异常 参数 。参数的存在和类型取决于异常类型。

except 子句可以在异常名称后面指定一个变量。这个变量和一个异常实例绑定,它的参数存储在 instance.args 中。为了方便起见,异常实例定义了 __str__() ,因此可以直接打印参数而无需引用 .args 。也可以在抛出之前首先实例化异常,并根据需要向其添加任何属性。:

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    # the exception instance
    print(inst.args)     # arguments stored in .args
    print(inst)          # __str__ allows args to be printed directly,
                          # but may be overridden in exception subclasses
    x, y = inst.args     # unpack args
    print('x =', x)
    print('y =', y)

# <class 'Exception'>
# ('spam', 'eggs')
# ('spam', 'eggs')
# x = spam
# y = eggs

如果异常有参数,则它们将作为未处理异常的消息的最后一部分('详细信息')打印。

异常处理程序不仅处理 try 子句中遇到的异常,还处理 try 子句中调用(即使是间接地)的函数内部发生的异常。例如:

def this_fails():
    x = 1/0

try:
    this_fails()
except ZeroDivisionError as err:
    print('Handling run-time error:', err)

# Handling run-time error: division by zero

4、触发异常

raise 语句支持强制触发指定的异常。例如:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: HiThere

raise 唯一的参数就是要触发的异常。这个参数必须是异常实例或异常类(派生自 Exception 的类)。如果传递的是异常类,将通过调用没有参数的构造函数来隐式实例化:

raise ValueError  # shorthand for 'raise ValueError()'

如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的 raise 语句形式重新引发异常

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: HiThere

5、异常链

raise 语句允许可选的 from 子句,它启用了链式异常。 例如:

# exc must be exception instance or None.
raise RuntimeError from exc

这在要转换异常时很有用。例如:

>>>

>>> def func():
...     raise IOError
...
>>> try:
...     func()
... except IOError as exc:
...     raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in func
OSError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database

异常链在 except 或 finally 子句触发异常时自动生成。禁用异常链可使用 from None 习语:

>>>

>>> try:
...     open('database.sqlite')
... except IOError:
...     raise RuntimeError from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError

异常链机制详见 内置异常

6、用户自定义异常

程序可以通过创建新的异常类来命名它们自己的异常(有关Python 类的更多信息,请参阅 )。异常通常应该直接或间接地从 Exception 类派生。

可以定义异常类,它可以执行任何其他类可以执行的任何操作,但通常保持简单,只提供一些属性,这些属性允许处理程序为异常提取有关错误的信息。 在创建可能引发多个不同错误的模块时,通常的做法是为该模块定义的异常创建基类,并为不同错误条件创建特定异常类的子类:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

大多数异常都定义为名称以“Error”结尾,类似于标准异常的命名。

许多标准模块定义了它们自己的异常,以报告它们定义的函数中可能出现的错误。有关类的更多信息,请参见  章节。

7、定义清理操作

try 语句有另一个可选子句,用于定义必须在所有情况下执行的清理操作。例如:

>>>

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

如果存在 finally 子句,则 finally 子句将作为 try 语句结束前的最后一项任务被执行。 finally 子句不论 try 语句是否产生了异常都会被执行。 以下几点讨论了当异常发生时一些更复杂的情况:

  • 如果在执行 try 子句期间发生了异常,该异常可由一个 except 子句进行处理。 如果异常没有被某个 except 子句所处理,则该异常会在 finally 子句执行之后被重新引发。

  • 异常也可能在 except 或 else 子句执行期间发生。 同样地,该异常会在 finally 子句执行之后被重新引发。

  • If the finally clause executes a breakcontinue or return statement, exceptions are not re-raised.

  • 如果在执行 try 语句时遇到一个 breakcontinue 或 return 语句,则 finally 子句将在执行 breakcontinue 或 return 语句之前被执行。

  • 如果 finally 子句中包含一个 return 语句,则返回值将来自 finally 子句的某个 return语句的返回值,而非来自 try 子句的 return 语句的返回值。

例如

>>>

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

一个更为复杂的例子:

>>>

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

正如你所看到的,finally 子句在任何情况下都会被执行。 两个字符串相除所引发的 TypeError不会由 except 子句处理,因此会在 finally 子句执行后被重新引发。

在实际应用程序中,finally 子句对于释放外部资源(例如文件或者网络连接)非常有用,无论是否成功使用资源。

8、预定义的清理操作

某些对象定义了在不再需要该对象时要执行的标准清理操作,无论使用该对象的操作是成功还是失败,清理操作都会被执行。 请查看下面的示例,它尝试打开一个文件并把其内容打印到屏幕上。:

for line in open("myfile.txt"):
    print(line, end="")

这个代码的问题在于,它在这部分代码执行完后,会使文件在一段不确定的时间内处于打开状态。这在简单脚本中不是问题,但对于较大的应用程序来说可能是个问题。 with 语句允许像文件这样的对象能够以一种确保它们得到及时和正确的清理的方式使用。:

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

执行完语句后,即使在处理行时遇到问题,文件 f 也始终会被关闭。和文件一样,提供预定义清理操作的对象将在其文档中指出这一点。

 

一、异常类型

1、Python 内置异常

BaseException 是所有内置异常的基类,但用户定义的类并不直接继承 BaseException,所有的异常类都是从 Exception 继承,且都在 exceptions 模块中定义。Python 自动将所有异常名称放在内建命名空间中,所以程序不必导入 exceptions 模块即可使用异常。一旦引发而且没有捕捉 SystemExit 异常,程序执行就会终止。如果交互式会话遇到一个未被捕捉的 SystemExit 异常,会话就会终止。

内置异常类的层次结构如下:

BaseException  # 所有异常的基类
 +-- SystemExit  # 解释器请求退出
 +-- KeyboardInterrupt  # 用户中断执行(通常是输入^C)
 +-- GeneratorExit  # 生成器(generator)发生异常来通知退出
 +-- Exception  # 常规异常的基类
      +-- StopIteration  # 迭代器没有更多的值
      +-- StopAsyncIteration  # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代
      +-- ArithmeticError  # 各种算术错误引发的内置异常的基类
      |    +-- FloatingPointError  # 浮点计算错误
      |    +-- OverflowError  # 数值运算结果太大无法表示
      |    +-- ZeroDivisionError  # 除(或取模)零 (所有数据类型)
      +-- AssertionError  # 当assert语句失败时引发
      +-- AttributeError  # 属性引用或赋值失败
      +-- BufferError  # 无法执行与缓冲区相关的操作时引发
      +-- EOFError  # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发
      +-- ImportError  # 导入模块/对象失败
      |    +-- ModuleNotFoundError  # 无法找到模块或在在sys.modules中找到None
      +-- LookupError  # 映射或序列上使用的键或索引无效时引发的异常的基类
      |    +-- IndexError  # 序列中没有此索引(index)
      |    +-- KeyError  # 映射中没有这个键
      +-- MemoryError  # 内存溢出错误(对于Python 解释器不是致命的)
      +-- NameError  # 未声明/初始化对象 (没有属性)
      |    +-- UnboundLocalError  # 访问未初始化的本地变量
      +-- OSError  # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类
      |    +-- BlockingIOError  # 操作将阻塞对象(e.g. socket)设置为非阻塞操作
      |    +-- ChildProcessError  # 在子进程上的操作失败
      |    +-- ConnectionError  # 与连接相关的异常的基类
      |    |    +-- BrokenPipeError  # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入
      |    |    +-- ConnectionAbortedError  # 连接尝试被对等方中止
      |    |    +-- ConnectionRefusedError  # 连接尝试被对等方拒绝
      |    |    +-- ConnectionResetError    # 连接由对等方重置
      |    +-- FileExistsError  # 创建已存在的文件或目录
      |    +-- FileNotFoundError  # 请求不存在的文件或目录
      |    +-- InterruptedError  # 系统调用被输入信号中断
      |    +-- IsADirectoryError  # 在目录上请求文件操作(例如 os.remove())
      |    +-- NotADirectoryError  # 在不是目录的事物上请求目录操作(例如 os.listdir())
      |    +-- PermissionError  # 尝试在没有足够访问权限的情况下运行操作
      |    +-- ProcessLookupError  # 给定进程不存在
      |    +-- TimeoutError  # 系统函数在系统级别超时
      +-- ReferenceError  # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象
      +-- RuntimeError  # 在检测到不属于任何其他类别的错误时触发
      |    +-- NotImplementedError  # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现
      |    +-- RecursionError  # 解释器检测到超出最大递归深度
      +-- SyntaxError  # Python 语法错误
      |    +-- IndentationError  # 缩进错误
      |         +-- TabError  # Tab和空格混用
      +-- SystemError  # 解释器发现内部错误
      +-- TypeError  # 操作或函数应用于不适当类型的对象
      +-- ValueError  # 操作或函数接收到具有正确类型但值不合适的参数
      |    +-- UnicodeError  # 发生与Unicode相关的编码或解码错误
      |         +-- UnicodeDecodeError  # Unicode解码错误
      |         +-- UnicodeEncodeError  # Unicode编码错误
      |         +-- UnicodeTranslateError  # Unicode转码错误
      +-- Warning  # 警告的基类
           +-- DeprecationWarning  # 有关已弃用功能的警告的基类
           +-- PendingDeprecationWarning  # 有关不推荐使用功能的警告的基类
           +-- RuntimeWarning  # 有关可疑的运行时行为的警告的基类
           +-- SyntaxWarning  # 关于可疑语法警告的基类
           +-- UserWarning  # 用户代码生成警告的基类
           +-- FutureWarning  # 有关已弃用功能的警告的基类
           +-- ImportWarning  # 关于模块导入时可能出错的警告的基类
           +-- UnicodeWarning  # 与Unicode相关的警告的基类
           +-- BytesWarning  # 与bytes和bytearray相关的警告的基类
           +-- ResourceWarning  # 与资源使用相关的警告的基类。被默认警告过滤器忽略。
 
详细说明请参考:https://docs.python.org/3/library/exceptions.html#base-classes

2、requests 模块的相关异常

要调用 requests 模块的内置异常,只要 “from requests.exceptions import xxx” 就可以了,比如:

from requests.exceptions import ConnectionError, ReadTimeout
或者直接这样也是可以的:

from requests import ConnectionError, ReadTimeout


requests模块内置异常类的层次结构如下:

IOError
 +-- RequestException  # 处理不确定的异常请求
      +-- HTTPError  # HTTP错误
      +-- ConnectionError  # 连接错误
      |    +-- ProxyError  # 代理错误
      |    +-- SSLError  # SSL错误
      |    +-- ConnectTimeout(+-- Timeout)  # (双重继承,下同)尝试连接到远程服务器时请求超时,产生此错误的请求可以安全地重试。
      +-- Timeout  # 请求超时
      |    +-- ReadTimeout  # 服务器未在指定的时间内发送任何数据
      +-- URLRequired  # 发出请求需要有效的URL
      +-- TooManyRedirects  # 重定向太多
      +-- MissingSchema(+-- ValueError) # 缺少URL架构(例如http或https)
      +-- InvalidSchema(+-- ValueError) # 无效的架构,有效架构请参见defaults.py
      +-- InvalidURL(+-- ValueError)  # 无效的URL
      |    +-- InvalidProxyURL  # 无效的代理URL
      +-- InvalidHeader(+-- ValueError)  # 无效的Header
      +-- ChunkedEncodingError  # 服务器声明了chunked编码但发送了一个无效的chunk
      +-- ContentDecodingError(+-- BaseHTTPError)  # 无法解码响应内容
      +-- StreamConsumedError(+-- TypeError)  # 此响应的内容已被使用
      +-- RetryError  # 自定义重试逻辑失败
      +-- UnrewindableBodyError  # 尝试倒回正文时,请求遇到错误
      +-- FileModeWarning(+-- DeprecationWarning)  # 文件以文本模式打开,但Requests确定其二进制长度
      +-- RequestsDependencyWarning  # 导入的依赖项与预期的版本范围不匹配
 
Warning
 +-- RequestsWarning  # 请求的基本警告
 
详细说明及源码请参考:http://www.python-requests.org/en/master/_modules/requests/exceptions/#RequestException

下面是一个简单的小例子,python内置了一个 ConnectionError 异常,这里可以不用再从 requests 模块 import 了:

import requests
from requests import ReadTimeout
 
 
def get_page(url):
    try:
        response = requests.get(url, timeout=1)
        if response.status_code == 200:
            return response.text
        else:
            print('Get Page Failed', response.status_code)
            return None
    except (ConnectionError, ReadTimeout):
        print('Crawling Failed', url)
        return None
 
 
def main():
    url = 'https://www.baidu.com'
    print(get_page(url))
 
 
if __name__ == '__main__':
    main()


3、用户自定义异常

异常应该是通过直接或间接的方式继承自 Exception 类。下面创建了一个MyError类,基类为Exception,用于在异常触发时输出更多的信息。

  在 try 语句块中,抛出用户自定义的异常后执行 except 部分,变量 e 是用于创建 MyError 类的实例。

class MyError(Exception):
    def __init__(self, msg):
        self.msg = msg
    
    def __str__(self):
        return self.msg
 
 
try:
    raise MyError('类型错误')
except MyError as e:
    print('My exception occurred', e.msg)

二、异常捕获

当发生异常时,我们就需要对异常进行捕获,然后进行相应的处理。python 的异常捕获常用 try...except... 结构,把可能发生错误的语句放在 try 模块里,用 except 来处理异常,每一个 try,都必须至少对应一个 except。此外,与 python 异常相关的关键字主要有:

关键字关键字说明
try/except捕获异常并处理
pass忽略异常
as定义异常实例(except MyError as e)
else如果 try 中的语句没有引发异常,则执行 else 中的语句
finally无论是否出现异常,都执行的代码
raise抛出/引发异常

    
    
    
    
    
    
        

 

 

 

1、捕获所有异常


包括键盘中断和程序退出请求(用 sys.exit() 就无法退出程序了,因为异常被捕获了),因此慎用。

try:
    pass 
except:
    print('异常说明')

2、捕获指定异常

try:
    pass
except <异常名>: 
      print('异常说明')

万能异常:

try:
    pass
except Exception: 
      print('异常说明')

一个例子:

try:
    f = open("file-not-exists", "r") 
except IOError as e: 
    print("open exception: %s: %s" %(e.errno, e.strerror))

3、捕获多个异常


捕获多个异常有两种方式,第一种是一个 except 同时处理多个异常,不区分优先级:

try:
    pass 
except (<异常名1>, <异常名2>, ...): 
    print('异常说明')

第二种是区分优先级的:

try:
    pass 
except <异常名1>: 
    print('异常说明1') 
except <异常名2>:
    print('异常说明2') 
except <异常名3>:
    print('异常说明3')

该种异常处理语法的规则是:

执行 try 下的语句,如果引发异常,则执行过程会跳到第一个 except 语句。
如果第一个 except 中定义的异常与引发的异常匹配,则执行该 except 中的语句。
如果引发的异常不匹配第一个 except,则会搜索第二个 except,允许编写的 except 数量没有限制。
如果所有的 except 都不匹配,则异常会传递到下一个调用本代码的最高层 try 代码中。

4、异常中的 else

如果判断完没有某些异常之后还想做其他事,就可以使用下面这样的 else 语句。

try:
    pass 
except <异常名1>: 
    print('异常说明1') 
except <异常名2>: 
    print('异常说明2')
else: 
    <语句>  # try语句中没有异常则执行此段代码

5、异常中的 finally


try...finally... 语句无论是否发生异常都将会执行最后的代码。

try:
    pass
finally:
    pass 

示例:

str1 = 'hello world'
try:
    int(str1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
else:
    print('try内没有异常')
finally:
    print('无论异常与否,都会执行我')

6、raise 主动触发异常

可以使用 raise 语句自己触发异常,raise 语法格式如下:

raise [Exception [, args [, traceback]]]

语句中 Exception 是异常的类型(例如 ValueError ),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是 "None"。最后一个参数是跟踪异常对象,也是可选的。

例子:

def not_zero(num):
    try:
        if num == 0:
            raise ValueError('参数错误')
        return num
    except Exception as e:
        print(e)
 
 
not_zero(0)

7、采用 traceback 模块查看异常

发生异常时,Python 能“记住”引发的异常以及程序的当前状态。Python 还维护着 traceback(跟踪)对象,其中含有异常发生时与函数调用堆栈有关的信息。记住,异常可能在一系列嵌套较深的函数调用中引发。程序调用每个函数时,Python 会在“函数调用堆栈”的起始处插入函数名。一旦异常被引发,Python 会搜索一个相应的异常处理程序。如果当前函数中没有异常处理程序,当前函数会终止执行,Python 会搜索当前函数的调用函数,并以此类推,直到发现匹配的异常处理程序,或者 Python 抵达主程序为止。这一查找合适的异常处理程序的过程就称为“堆栈辗转开解”(StackUnwinding)。解释器一方面维护着与放置堆栈中的函数有关的信息,另一方面也维护着与已从堆栈中“辗转开解”的函数有关的信息。

格式如下:

try:
    block 
except: 
    traceback.print_exc()

例子:

try:
    1/0
except Exception as e:
    print(e)

如果我们这样写的话,程序只会报 “division by zero” 错误,但是我们并不知道是在哪个文件哪个函数哪一行出的错。

下面使用 traceback 模块,官方参考文档:https://docs.python.org/2/library/traceback.html

import traceback
 
try:
    1/0
except Exception as e:
    traceback.print_exc()

这样就会帮我们追溯到出错点:

Traceback (most recent call last):
  File "E:/PycharmProjects/ProxyPool-master/proxypool/test.py", line 4, in <module>
    1/0
ZeroDivisionError: division by zero

另外,traceback.print_exc()跟traceback.format_exc()有什么区别呢?

区别就是,format_exc() 返回字符串,print_exc() 则直接给打印出来。即 traceback.print_exc()与print(traceback.format_exc()) 效果是一样的。print_exc() 还可以接受 file 参数直接写入到一个文件。比如可以像下面这样把相关信息写入到tb.txt文件去。

traceback.print_exc(file=open('tb.txt','w+'))

Python 内置了几十种常见的异常,就在 builtins 模块内。所有的异常都是异常类,首字母是大写的!

在发生异常的时候,Python 会打印出异常信息,信息的前面部分显示了异常发生的上下文环境,并以调用栈的形式显示具体信息。异常类型作为信息的一部分也会被打印出来,例如 ZeroDivisionError,NameError 和 TypeError。

>>> 10 * (1/0)
ZeroDivisionError: division by zero

>>> 4 + spam*3
NameError: name 'spam' is not defined

>>> '2' + 2
TypeError: Can't convert 'int' object to str implicitly

Python 内置了一套 try...except...finally(else)...的异常处理机制:

try:
    pass
except Exception as e:
    pass

Python 的异常机制具有嵌套处理的能力,比如下面的函数 f3() 调用 f2(),f2() 调用 f1(),虽然是在 f1() 出错了,但只需要在 f3() 进行异常捕获,不需要每一层都捕获异常。

def f1():
    return 10/0
def f2():
    f1()
def f3():
    f2()
f3()

# ZeroDivisionError: division by zero

仅仅需要在调用 f3() 函数的时候捕获异常:

try:
    f3()
except ZeroDivisionError as e:
    print(e)

try…except… 语句处理异常的工作机制如下:

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)
  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
try:
    print("发生异常之前的语句正常执行")
    print(1/0)
    print("发生异常之后的语句不会被执行")
except ZeroDivisionError as e:
    print(e)

# division by zero
  • 如果程序发生的异常不在你的捕获列表中,那么依然会抛出别的异常。
  • 未捕获到异常,程序直接报错
s1 = 'hello'
try:
    int(s1)
except IndexError as e:    # 本例为非法值异常,而你只捕获索引异常
    print(e)

# ValueError: invalid literal for int() with base 10: 'hello'
  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。也就是前面说的嵌套处理能力。直到程序最顶端如果还没有被捕获,那么将弹出异常。
try:
    try:
        print("发生异常之前的语句正常执行")
        print(1/0)
        print("发生异常之后的语句不会被执行")
    except ValueError as e:
        print(e)
except ZeroDivisionError as e:
    print("里层没有抓好,只能辛苦我外层了")
  • 可能包含多个 except 子句,分别来处理不同的特定的异常。但最多只有一个分支会被执行。所以 except 子句有排序先后问题,进了一条巷子就不会进别的巷子。
try:
    print("发生异常之前的语句正常执行")
    print(1/0)
    print("发生异常之后的语句不会被执行")
except NameError as e:
    print(e)
except ZeroDivisionError as e:
    print("我是第一个抓取到除零异常的")
except (ValueError,ZeroDivisionError) as e:
    print("我是备胎")
  • 处理程序将只针对对应的 try 子句中的异常进行处理,不会处理其他 try 语句中的异常。
  • 一个 except 子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组。
except (RuntimeError, TypeError, NameError):
        pass
  • 最后一个 except 子句可以忽略异常的名称,它将被当作通配符使用,也就是说匹配所有异常。
except:
    print("Unexpected error:", sys.exc_info()[0])

看一个综合性的例子:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as e:
    print("OS error: {0}".format(e))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

通用异常:Exception

在 Python 的异常中,有一个通用异常:Exception,它可以捕获任意异常。

s1 = 'hello'
try:
    int(s1)
except Exception as e:
    print('错误')

那么既然有这个什么都能管的异常,其他诸如 OSError、ValueError 的异常是不是就可以不需要了?当然不是!很多时候程序只会弹出那么几个异常,没有必要针对所有的异常进行捕获,那样的效率会很低。另外,根据不同的异常种类,制定不同的处理措施,用于准确判断错误类型,存储错误日志,都是非常有必要甚至强制的。

finally 和 else 子句

try except 语法还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。这个子句将在 try 子句没有发生任何异常的时候执行。

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

同样的,还有一个可选的 finally 子句。无论 try 执行情况和 except 异常触发情况,finally 子句都会被执行!

try:
    print('try...')
    r = 10 / int('a')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
finally:
    print('finally...')
print('END')

那么,当 else 和 finally 同时存在时:

try:
    pass
except:
    pass
else:
    print("else")
finally:
    print("finally")

运行结果:

else

finally

如果有异常发生:

try:
    1/0
except:
    pass
else:
    print("else")
finally:
    print("finally")

运行结果:

finally

主动抛出异常:raise

很多时候,我们需要主动抛出一个异常。Python 内置了一个关键字 raise,可以主动触发异常。

>>> raise
RuntimeError: No active exception to reraise

>>> raise NameError("kkk")
NameError: kkk

raise 唯一的一个参数指定了要被抛出的异常的实例,如果什么参数都不给,那么会默认抛出当前异常。

为什么要自己主动抛出异常?因为有的时候,你需要记录错误信息,然后将异常继续往上层传递,让上层去处理异常,如下:

try:
    1/0
except ZeroDivisionError as e:
    print("记录异常日志: ", e)
    print("但是我自己无法处理,只能继续抛出,看看上层能否处理(甩锅)")
    raise

有时候,你需要主动弹出异常,作为警告或特殊处理:

sex = int(input("Please input a number: "))

try:
    if sex == 1:
        print("这是个男人!")
    elif sex == 0:
        print("这是个女人!")
    else:
        print("好像有什么不符合常理的事情发生了!!")
        raise ValueError("非法的输入")
except ValueError:
    print("这是个人妖!")

更多的时候,你需要使用 raise 抛出你自定义的异常,如下面所述!

自定义异常

Python 内置了很多的异常类,并且这些类都是从 BaseException 类派生的。

下面是一些常见异常类,请把它们记下来!这样你在见到大多数异常的时候都能快速准确的判断异常类型。

异常名

解释

AttributeError

试图访问一个对象没有的属性

IOError

输入/输出异常

ImportError

无法引入模块或包;多是路径问题或名称错误

IndentationError

缩进错误

IndexError

下标索引错误

KeyError

试图访问不存在的键

KeyboardInterrupt

Ctrl+C被按下,键盘终止输入

NameError

使用未定义的变量

SyntaxError

语法错误

TypeError

传入对象的类型与要求的不符合

UnboundLocalError

试图访问一个还未被设置的局部变量

ValueError

传入一个调用者不期望的值,即使值的类型是正确的

OSError

操作系统执行错误

大多数情况下,上面的内置异常已经够用了,但是有时候你还是需要自定义一些异常。自定义异常应该继承 Exception 类,直接继承或者间接继承都可以,例如:

class MyExcept(Exception):
    def __init__(self, msg):
        self.message = msg

    def __str__(self):
        return self.message

try:
    raise MyExcept('我的异常!')
except MyExcept as ex:
    print(ex) 

异常的名字都以 Error 结尾,我们在为自定义异常命名的时候也需要遵守这一规范,就跟标准的异常命名一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值