python3測試工具開發快速入門教程8錯誤和異常

Python 中(至少)有兩種錯誤:語法錯誤(syntax errors)和異常(exceptions)。

語法錯誤

語法錯誤又稱作解析錯誤:

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

語法分析器指出錯誤行,並且在檢測到錯誤的位置前面顯示^。

異常

即使語句或表達式在語法上是正確的,執行時也可能會引發錯誤。運行期檢測到的錯誤稱爲異常

#!python
>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

錯誤信息的最後一行指出發生了什麼錯誤。異常也有不同的類型,異常類型做爲錯誤信息的一部分顯示出來:示例中的異常分別爲 ZeroDivisionError, NameErrorTypeError。打印錯誤信息時,異常的類型作爲異常的內置名顯示。對於所有的內置異常都是如此,不過用戶自定義異常就不一定了(儘管這是很有用的約定)。標準異常名是內置的標識符(非保留關鍵字)。

這行後一部分是關於該異常類型的詳細說明,這意味着它的內容依賴於異常類型。

錯誤信息的前半部分以堆棧的形式列出異常發生的位置。通常在堆棧中列出了源代碼行,然而,來自標準輸入的源碼不會顯示出來。

內置的異常 列出了內置異常和它們的含義。

異常處理

例子:要求用戶輸入直到輸入合法的整數爲止,但允許用戶中斷這個程序(使用Control-C或系統支持的任何方法)。注意:用戶產生的中斷會引發 KeyboardInterrupt 異常。

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

try 語句按如下方式工作。

  • 首先,執行try和except 之間的內容
  • 如果沒有異常發生,忽略except語句。
  • 如果在 try 子句執行過程中發生了異常,那麼該子句其餘的部分就會忽略。如果異常匹配於 * 後面指定的異常類型,就執行對應的except子句。然後繼續執行try語句之後的代碼。
  • 如果發生了一個異常,在except 子句中沒有與之匹配的分支,它就會傳遞到上一級try 語句中。如果最終仍找不到對應的處理語句,它就成爲未處理異常,終止程序運行,顯示提示信息。

try語句可能包含多個 except 子句,分別指定處理不同的異常。至多執行一個分支。異常處理程序只會處理對應的 try 子句中發生的異常,在同一try語句中,其他子句中發生的異常則不作處理。except 子句可以在元組中列出多個異常的名字,例如:

#!python
... except (RuntimeError, TypeError, NameError):
...     pass

異常匹配如果except子句中的類相同的類或其基類(反之如果是其子類則不行)。 例如,以下代碼將按此順序打印B,C,D:

#!python
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 B在先,將打印B,B,B。

最後except子句可以省略例外名稱,作爲通配符。 請謹慎使用此功能,因爲這種方式很容易掩蓋真正的編程錯誤! 它也可以用來打印錯誤消息,然後重新引發異常(也允許調用者處理異常):

#!python
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 語句沒有拋出異常時,需要執行一些代碼,可以使用這個子句。例如:

#!python
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。也可以在引發異常之前初始化異常,並根據需要向其添加屬性。

#!python
>>> 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 子句中調用的函數內部發生的異常。例如:

#!python
>>> 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

拋出異常

raise 語句可強行拋出指定的異常。例如:

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

要拋出的異常由raise的唯一參數標識。它必需是異常實例或異常類(繼承自 Exception 的類)。如果傳遞異常類,它將通過調用它的沒有參數的構造函數而隱式實例化:

#!python
raise ValueError  # shorthand for 'raise ValueError()'

如果你想知道異常是否拋出,但不想處理它,raise語句可簡單的重新拋出該異常:

#!python
>>> 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

用戶自定義異常

直接或間接的繼承 Exception 可以自定義異常。

異常類中可以定義任何其它類中可以定義的東西,但是通常爲了保持簡單,只在其中加入幾個屬性信息,以供異常處理句柄提取。如果新創建的模塊中需要拋出幾種不同的錯誤時,通常的作法是爲該模塊定義異常基類,然後針對不同的錯誤類型派生出對應的異常子類:

#!python
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” 結尾。

很多標準模塊中都定義了自己的異常,以報告在他們所定義的函數中可能發生的錯誤。關於類的進一步信息請參見Classes

定義清理行爲

try 語句有可選的子句定義在任何情況下都一定要執行的清理操作。例如:

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

不管有沒有發生異常,finally子句在程序離開try前都會被執行。當語句中發生了未捕獲的異常(或者它發生在except或else子句中),在執行完finally子句後它會被重新拋出。 try語句經由break、continue或return語句退出也會執行finally子句。

#!python
>>> 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子句多用於釋放外部資源(文件或網絡連接之類的)。

預定義清理行爲

有些對象定義了標準的清理行爲,無論對象操作是否成功,不再需要該對象的時候就會起作用。以下示例嘗試打開文件並把內容輸出到屏幕。

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

這段代碼的問題在於在代碼執行完後沒有立即關閉打開的文件。簡單的腳本里沒什麼,但是大型應用程序就會出問題。with 語句使得文件之類的對象能及時準確地進行清理。

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

語句執行後,文件f總會被關閉,即使是在處理文件行時出錯也一樣。其它對象是否提供了預定義的清理行爲要參考相關文檔。

參考資料

異常處理實例: 正則表達式及拼音排序

有某羣的某段聊天記錄

現在要求輸出排序的qq名,結果類似如下:

#!python

[..., '本草隱士', 'jerryyu', '可憐的櫻桃樹', '叻風雲', '歐陽-深圳白芒',  ...]
    

需求來源:有個想批量邀請某些qq羣的活躍用戶到自己的羣。又不想鋪天蓋地去看聊天記錄。

參考資料:python文本處理庫

參考代碼:

#!python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Author:    [email protected] wechat:pythontesting qq:37391319
# 技術支持 釘釘羣:21745728(可以加釘釘pythontesting邀請加入) 
# qq羣:144081101 591302926  567351477
# CreateDate: 2018-6-1

import re
from pypinyin import lazy_pinyin

name = r'test.txt'

text = open(name,encoding='utf-8').read()
#print(text)

results = re.findall(r'(:\d+)\s(.*?)\(\d+', text)

names = set()
for item in results:
    names.add(item[1])  

keys = list(names)
keys = sorted(keys)

def compare(char):
    try:
        result = lazy_pinyin(char)[0][0]
    except Exception as e:
        result = char
    return result
    
keys.sort(key=compare)
print(keys)
    

執行示例:

1,把qq羣的聊天記錄導出爲txt格式,重命名爲test.txt

2, 執行:

#!python

$ python3 qq.py 
['Sally', '^^O^^', 'aa催乳師', 'bling', '本草隱士', '純中藥治療陽痿早泄', '長夜無荒', '東方~慈航', '幹金草', '廣東-曾超慶', '紅梅* 渝', 'jerryyu', '可憐的櫻桃樹', '叻風雲', '歐陽-深圳白芒', '勝昔堂~元亨', '蜀中~眉豆。', '陝西渭南逸清閣*無爲', '吳寧……任', '系統消息', '於立偉', '倚窗望嶽', '煙霞靄靄', '燕子', '張強', '滋味', '✾買個罐頭 吃西餐', '【大俠】好好', '【大俠】面向大海~純中藥治燙傷', '【宗師】吳寧……任', '【宗師】紅梅* 渝', '【少俠】焚琴煮鶴', '【少俠】笨笨', '【掌門】漵浦☞山野人家']
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章