1. 程式人生 > 實用技巧 >Python-4 異常、除錯、測試、IO

Python-4 異常、除錯、測試、IO

異常

  1. 丟擲、捕獲異常 try... except... finally...
try:
    x = 1
    raise TypeError('型別錯誤!')
except TypeError as e: # 使用as的方式,錯誤型別會包括子類錯誤
    print('型別錯誤:', e.value)
else: # 成功執行,沒有except的else,可選
    print('成功運行了')
finally:
    print('finally...')
print('END')
  • 一般儘量使用 Python 內建的錯誤型別。
  • except 裡,可以使用不帶引數的 raise
    ,這將繼續將所捕獲的錯誤繼續向上丟擲。
  1. logging 模組
    except 中,使用 logging.exception(e) 可以記錄下錯誤,但還可以使程式繼續執行。
  • 也可以將錯誤資訊輸出到檔案中。
logging.basicConfig(level=logging.INFO)
logging.info('n = %d' % n)
print(10 / n)
  • 還有:debuginfowarningerror
  1. with 關鍵詞
    with 是一種簡化的 try... except... finally...
with open('x.txt') as fp:  
    print fp.read()
  • 注意with 後面處理的物件必須有 __enter__()__exit__() 這兩個方法。
  • with 在執行前先執行 __enter__(),然後在 finally
    執行 __exit__()
    • 確保了執行 finally,做好一般的善後和異常處理,一般在資原始檔裡使用較多。

除錯

  1. 使用斷言
# n應該不為0
 assert n != 0, 'n is zero!'

使用斷言,如果不符合條件(n為0),則會列印後面的資訊並丟擲 AssertionError

  • python -O err.py:不執行 assert。相當於 pass
  1. 偵錯程式 pdb,單步執行
  2. 設定斷點:pdb.set_trace()

單元測試

編寫一些測試條件,測試所有可能出現的代表情況,通過測試說明程式碼基本可行。

  1. mydict.py程式碼如下:
class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value
  1. 單元測試
import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

if __name__ == '__main__': # 設定這個語句,即當成普通的py指令碼執行測試
    unittest.main()
  • 繼承自 unittest.TestCase
  • test 開頭的函式才會被視為測試方法執行。
  • 最常用 assertEqual() 判斷結果是否符合預期。
  • 測試能否丟擲正確的異常:
# 訪問d['empty']丟擲錯誤
with self.assertRaises(KeyError):
    value = d['empty']
  • 另一種執行測試檔案的方法:python -m unittest mydict_test
  • setUptearDown:這兩個特殊的函式會在執行每個測試方法前後被呼叫,可以用來連線資料庫等。

文件測試

在類的文件中,Python 的“文件測試”(doctest)模組可以直接提取註釋中的程式碼並執行測試。
比如重寫上述的模組:

class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
# ...

那麼在呼叫該檔案時,如果沒有顯示錯誤,說明文件測試通過。

  • 文件測試只會在命令列直接執行的時候才會被呼叫,平時匯入模組裡不會被呼叫。

IO 檔案操作

  • 外發資料 Ouput,外部發送資料來 Input。
  • 同步 IO:會堵塞等待;非同步 IO:繼續執行,跳過等待結果,有回撥模式,輪詢模式(輪詢是主執行緒主動多次去查詢結果,回撥是 IO 完子執行緒會執行的)。

在磁碟上讀寫檔案的功能都是由作業系統提供的,現代作業系統不允許普通的程式直接操作磁碟,所以,讀寫檔案就是請求作業系統開啟一個檔案物件(通常稱為檔案描述符),然後,通過作業系統提供的介面從這個檔案物件中讀取資料(讀檔案),或者把資料寫入這個檔案物件(寫檔案)。

  • 讀完檔案都需要關閉檔案,避免佔用系統資源,需要 finally/with
file object  = open(file_name [, access_mode][, buffering])
  • file_name:目標檔案,可以包括檔案的路徑。
  • access_mode:開啟檔案的模式,預設只讀。
  • buffering:緩衝。
    • 0 關閉(僅二進位制模式)。1 開啟行緩衝(僅文字模式)。大於 1 的表示設定緩衝區大小。負數表示使用系統預設緩衝區大小。
  • 相當於字元流,位元組流。

有關模式:

Character Meaning
'r' 只讀模式 (預設 rt)。
'w' 寫入模式,檔案存在則清空重新寫,不存在會自動建立。
'x' 獨佔寫模式,新建一個檔案,如果該檔案已存在則會報錯。
'a' 追加寫入模式,檔案存在追加寫入,不存在會自動建立。
'b' 二進位制模式。
't' 文字模式(預設 rt)。
'+' 開啟一個檔案進行更新(可讀可寫)。

os 模組

  • os.name:返回當前系統的名稱。
    • posixLinuxUnixMac OS X
    • ntWindows
  • os.rename(current_file_name, new_file_name):重新命名檔案。
  • os.remove(file_name):刪除檔案。
  • os.mkdir("newdir"):建立目錄。
  • os.chdir("newdir"):改變當前目錄。
  • os.getcwd():返回當前所在目錄。
  • os.rmdir('dirname'):刪除目錄,該目錄下的檔案需要先刪乾淨。
  • 操作一個路徑的字串:
    • os.path.join():拼接路徑,可以自動識別路徑分隔符。
    • os.path.split():拆分出路徑裡末尾的檔案/目錄,返回兩個元素的列表。可以自動識別路徑分隔符。
    • os.path.splitext():拆分出路徑裡檔案的副檔名。
  • shutil 模組提供了 copyfile() 函式,相當於 os 模組的補充。
# 當前目錄下的所有目錄
L = [x for x in os.listdir('.') if os.path.isdir(x)]
# 當前目錄下的所有.py檔案
L2 = [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1] == '.py']

序列化

將儲存在記憶體中的變數等變成可儲存或可傳輸的樣式。

在 Python 中叫 pickling,在其他語言中也被稱之為 serialization,marshalling,flattening 等。反之叫做反序列化,unpickling。

  • pickle.dumps():將任意物件序列化成二進位制 bytes,可以將二進位制寫到檔案中。
  • pickle.dump():將物件序列化寫入到一個 file-like Object(有 read() 方法)中。
import pickle
d = dict(name='Bob', age=20, score=88)

dbytes = pickle.dumps(d) # 是一個二進位制資料

f = open('dump.txt', 'wb')
pickle.dump(d, f) # 將物件d轉儲到f中。
f.close()

dump 英 [dʌmp] 美 [dʌmp]
v. 傾倒;丟下;與(某人)結束戀愛關係;卸出(資料);(記憶體資訊)轉儲
n. 垃圾場;廢渣堆;軍需品臨時存放處;轉儲
理解:將物件從記憶體中倒出來,變成可儲存的檔案。

  • pickle.loads():將二進位制 bytes 反序列化為物件。可以先將檔案讀取到二進位制,然後使用該函式。
  • pickle.load():將 file-like Object 直接反序列化出物件。
f = open('dump.txt', 'rb')
d = pickle.load(f) # 從f中反序列化,產生一個相同內容的物件!
f.close()
print(d)
# {'age': 20, 'score': 88, 'name': 'Bob'}

JSON

  • json.dump():直接把 JSON 寫入一個 file-like Object
  • json.dumps():將物件轉成 JSON 的字串:
import json
d = dict(name='Bob', age=20, score=88)
print(json.dumps(d)) # 返回字串
# {"age": 20, "score": 88, "name": "Bob"}
  • json.loads():將 JSON 的字串反序列化。json.loads(json_str)
  • json.load():從 file-like Object 中讀取字串並反序列化。
  • 總結:有 s 表示與與檔案無關,無 s 的表示直接與檔案流相關。
序列化一個類

在類中定義方法返回字典,使用 json.dumps(s, default=student2dict)) 即可將類例項 s 轉為 JSON 字串。

# 類轉為字典
def student2dict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }
# 字典轉為類
def dict2student(d):
    return Student(d['name'], d['age'], d['score'])

通常 class 的例項都有一個 __dict__ 屬性,它就是一個 dict,用來儲存例項變數。也有少數例外,比如定義了 __slots__ 的class。