學習python的日常6
錯誤、調試和測試:
錯誤處理:
try: print(‘try...‘) r = 10 / 0 print(‘result:‘, r) except ZeroDivisionError as e: print(‘except:‘, e) finally: print(‘finally...‘) print(‘END‘)
首先是執行語句,然後發現錯誤了就會跳轉到執行except,語句,然後按順序執行,如果是正確的就不會執行except語句。
其中的Error還可以細分,錯誤本身也是一個類,都繼承自BaseException,所以盡量不要出現錯誤的父類和子類同時捕獲,
因為這時候只會執行父類的捕獲錯誤。
調用棧:
如果一個錯誤沒有被捕獲,就會一直往上拋,最後被python解釋器捕獲,出錯的時候通過分析錯誤的調用棧信息,可以定位
錯誤的位置。
記錄錯誤:
python內置的logging模塊可以非常容易地記錄錯誤信息
# err_logging.py import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar(‘0‘) except Exception as e: logging.exception(e) main() print(‘END‘) $ python3 err_logging.py ERROR:root:division by zero Traceback (most recent call last): File "err_logging.py", line 13, in main bar(‘0‘) File "err_logging.py", line 9, in bar return foo(s) * 2 File "err_logging.py", line 6, in foo return 10 / int(s) ZeroDivisionError: division by zero END
拋出錯誤:
因為錯誤是class,然後捕獲一個錯誤就是捕獲到一個實例,如果要拋出錯誤,可以根據需要定義一個錯誤的class,
選擇好繼承關系,然後用raise語句拋出一個錯誤的實例。只有在必要的時候才定義我們自己的錯誤類型,如果可以,
盡量使用python內置的錯誤類型。
最後還有一種錯誤處理的方式:
# err_reraise.py def foo(s): n = int(s) if n==0: raise ValueError(‘invalid value: %s‘ % s) return 10 / n def bar(): try: foo(‘0‘) except ValueError as e: print(‘ValueError!‘) raise bar()
捕獲異常後,又把錯誤用過raise語句拋出去,是因為,捕獲錯誤的目的只是記錄一下,由於當前函數不知道
應該怎麽處理該錯誤,所以往上拋是一種很好的方式,最終會讓頂層調用者去處理。
調試:
1.一種方法簡單粗暴,直接用print()打印。
2.斷言,凡是用print()來輔助查看的都可以用斷言(assert)來替代。
3.logging,把print()替換為logging是第三種方式,logging可以指定記錄信息的級別,有debug,info,warning,error
等幾個級別,指定高級別的時候,低級別就不起作用了。
4.pdb,啟用python的調試器pdb,讓程序以單步方式運行,可以隨時查看運行狀態。pdb.set_trace(),也是用pdb,只
需要導入pdb,然後在可能出錯的地方放一個pdb.set_trace(),就可以設置一個斷點,到了之後就會暫停並進入pdb
調試環境,用p可以查看變量,用c繼續運行。
5.IDE,支持調試功能的IDE,有一些比較好的Python IDE。
單元測試:
用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工作。我們給出一系列數據,然後期望輸出的結果是我們
預期的結果,如果不相符就說明我們的代碼中存在問題。
文檔測試:
利用註釋來告知代碼我們希望得到的結果,然後又一些工具來自動生成文檔。
IO編程:
IO指的是Input/Output,也就是輸入和輸出。IO編程中由於內外設備的速度不匹配,又可以分為同步IO和異步IO兩種方式,
現在涉及到的是同步IO
文件讀寫:
讀文件
>>> f = open(‘/Users/michael/test.txt‘, ‘r‘)
如果不存在就會報錯,然後如果成功打開調用read()方法就可以一次性讀到內存當中,用一個str對象表示,要記得調用
close()方法來關閉文件,如果出錯是無法關閉文件的,f.close()也不會調用,所以用try...finally來實現,但是總寫會很麻煩,
所以可以調用python的with語句來自動調用close()方法:
with open(‘/path/to/file‘, ‘r‘) as f: print(f.read())
想open()函數這種返回有個read()方法的對象,在python中統稱為file-like Object,除了file外還有很多其他的流,不過只要
有個read()方法就可以了。讀
>>> from io import StringIO >>> f = StringIO() >>> f.write(‘hello‘) 5 >>> f.write(‘ ‘) 1 >>> f.write(‘world!‘) 6 >>> print(f.getvalue()) hello world!
操作二進制數據就需要使用BytesIO。
StringIO和BytesIO是在內存中操作str和bytes的方法,使得和讀寫文件具有一致的接口。
取二進制文件,用’rb‘模式打開文件就行;讀取字符編碼文本文件,添加一個encoding=’gkb‘,遇到
不規範的可以添加參數errors=‘ignore‘。
寫文件
>>> f = open(‘/Users/michael/test.txt‘, ‘w‘) >>> f.write(‘Hello, world!‘) >>> f.close()
寫文件和讀文件一樣,不過區別在於傳入的標識符,‘w‘或者’wb‘表示文本文件或者寫二進制文件,如果要寫入特定編碼的文本
文件,要給open()傳入encoding參數,將字符串自動轉換成特定編碼。
StringIO和BytesIO
操作文件和目錄:
python的os模塊封裝了操作系統的目錄和文件操作,這些函數有的在os模塊中,有的在os.path模塊中。
序列化:
把變量從內存中變成可存儲或可傳輸的過程稱之為序列化,反過來把變量內容從序列化的對象重新讀到內存裏稱之為反序列化。
python提供pickle模塊來實現序列化,但是要把序列變得更通用、更符合Web標準,就可以使用json模塊。
json模塊的dumps()和loads()函數是用來序列化和反序列化的,如果默認的序列化和反序列化機制不滿足要求時,可以傳入更多
的參數來定制序列化或反序列化規則。
進程和線程:
多任務的實現方式有3種:1.多進程模式;
2.多線程模式;
3.多進程+多線程模式
線程時最小的執行單元,而進程由至少一個線程組成。
多進程:
在Unix/Linux下,可以使用fork()調用實現多進程。
如果要實現跨平臺的多進程,可以使用multiprocessing模塊。
subprocess模塊可以很方便地啟動一個子進程,然後控制其輸入和輸出。
進程間通信時通過Queue、Pipes等實現地。
多線程:
同一個進程內多個線程,python地標準庫提供了_thread和threading兩個模塊,絕大多數下使用threading這個高級模塊。
啟動一個線程就是把一個函數傳入並創建Thread實例,然後調用start()開始執行。多線程中,由於線程地調度是由系統
決定地,當線程交替執行時,很容易造成內容被亂改。為了確保變量,可以給線程上鎖,一個進程提供一個鎖,然後讓
線程去獲取該鎖,這樣線程就只有在獲得鎖的情況下才能執行語句,就不會造成沖突了。然後壞處的話只要時降低了
效率以及容易造成死鎖。Python的解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,
必須先獲得GIL鎖。這個GIL全局鎖實際上把所有線程的執行代碼都給上鎖。
ThreadLocal:
import threading # 創建全局ThreadLocal對象: local_school = threading.local() def process_student(): # 獲取當前線程關聯的student: std = local_school.student print(‘Hello, %s (in %s)‘ % (std, threading.current_thread().name)) def process_thread(name): # 綁定ThreadLocal的student: local_school.student = name process_student() t1 = threading.Thread(target= process_thread, args=(‘Alice‘,), name=‘Thread-A‘) t2 = threading.Thread(target= process_thread, args=(‘Bob‘,), name=‘Thread-B‘) t1.start() t2.start() t1.join() t2.join()
局部變量在函數調用時很麻煩,一層一層傳遞很麻煩,全局變量local_school就是一個ThreadLocal對象,每個Thread
對它都可以讀寫student屬性,但互不影響。可以把local_school看成全局變量,每個屬性如local_school.student都是線
程的局部變量,可以任意讀寫而互不幹擾,也不用管理鎖的問題,ThreadLocal內部會處理。
ThreadLocal最常用的地方就是為每個線程綁定一個數據庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調
用到的處理函數都可以非常方便地訪問這些資源。一個ThreadLocal變量雖然是全局變量,但每個線程都只能讀寫自己
線程的獨立副本,互不幹擾。ThreadLocal解決了參數在一個線程中各個函數都可以非常方便地訪問這些資源。
分布式進程:
Python地分布式進程接口簡單,封裝良好,適合需要把繁重任務分布到多臺機器地環境下。
註意Queue地作用是用來傳遞任務和接收結果,每個任務的描述數據量要盡量小。比如發送一個處理日誌文件的任務,
就不要發送幾百兆的日誌文件本身,而是發送日誌文件存放的完整路徑,由Worker進程再去共享的磁盤上讀取文件。
學習python的日常6