Python除錯終極指南 - martinheinz
即使您編寫清晰易讀的程式碼,即使使用測試覆蓋了程式碼,即使您是非常有經驗的開發人員,也不可避免地會出現奇怪的錯誤,並且您將需要以某種方式進行除錯。許多人只使用一堆print語句來檢視程式碼中正在發生的事情。這種方法遠非理想,還有很多更好的方法來找出您的程式碼出了什麼問題,我們將在本文中探討其中的一些方法。
記錄是必須的
如果您編寫的應用程式沒有某種日誌設定,您最終會後悔的。您的應用程式中沒有任何日誌可能會很難對所有錯誤進行故障排除。幸運的是-在Python中-設定基本記錄器非常簡單:
import logging logging.basicConfig( filename='application.log', level=logging.WARNING, format= '[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', datefmt='%H:%M:%S' ) logging.error("Some serious error occurred.") logging.warning('Function you are using is deprecated.') |
這就是開始將日誌寫入檔案的所有操作,該日誌看起來像這樣(您可以使用來找到檔案的路徑logging.getLoggerClass().root.handlers[0].baseFilename):
[12:52:35] {<stdin>:1} ERROR - Some serious error occurred. <p class="indent">[12:52:35] {<stdin>:1} WARNING - Function you are using is deprecated. |
這種設定看起來似乎已經足夠好了(通常是這樣),但是配置合理,格式清晰,可讀性強的日誌可以使您的生活更加輕鬆。改善和擴大配置的一種方法是使用.ini或.yaml檔案被由記錄器讀取。作為您可以在配置中執行的操作的示例:
version: 1 disable_existing_loggers: true formatters: standard: format: "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s" datefmt: '%H:%M:%S' handlers: console: # handler which will log into stdout class: logging.StreamHandler level: DEBUG formatter: standard # Use formatter defined above stream: ext://sys.stdout file: # handler which will log into file class: logging.handlers.RotatingFileHandler level: WARNING formatter: standard # Use formatter defined above filename: /tmp/warnings.log maxBytes: 10485760 # 10MB backupCount: 10 encoding: utf8 root: # Loggers are organized in hierarchy - this is the root logger config level: ERROR handlers: [console, file] # Attaches both handler defined above loggers: # Defines descendants of root logger mymodule: # Logger for "mymodule" level: INFO handlers: [file] # Will only use "file" handler defined above propagate: no # Will not propagate logs to "root" logger |
在您的python程式碼中擁有這種廣泛的配置將很難導航,編輯和維護。將內容儲存在YAML檔案中,可以使用上述非常特定的設定輕鬆設定和調整多個記錄器。
如果您想知道所有這些配置欄位的來源,請在此處進行記錄,它們中的大多數只是關鍵字引數,如第一個示例所示。
因此,現在在檔案中包含配置,意味著我們需要以某種方式載入。使用YAML檔案的最簡單方法是:
import yaml from logging import config with open("config.yaml", 'rt') as f: config_data = yaml.safe_load(f.read()) config.dictConfig(config_data) |
Python記錄器實際上並不直接支援YAML檔案,但它支援字典配置,可以使用yaml.safe_load輕鬆從YAML中建立。如果您傾向於使用舊.ini檔案,那麼我只想指出,作為一種docs,對於新應用程式,建議使用字典配置。有關更多示例,請檢視日誌記錄手冊。
日誌記錄裝飾器
使用日誌記錄修飾器代替修改函式的主體,該修飾符將記錄具有特定日誌級別和可選訊息的每個函式呼叫。讓我們看一下裝飾器:
from functools import wraps, partial import logging def attach_wrapper(obj, func=None): # Helper function that attaches function as attribute of an object if func is None: return partial(attach_wrapper, obj) setattr(obj, func.__name__, func) return func def log(level, message): # Actual decorator def decorate(func): logger = logging.getLogger(func.__module__) # Setup logger formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler) log_message = f"{func.__name__} - {message}" @wraps(func) def wrapper(*args, **kwargs): # Logs the message and before executing the decorated function logger.log(level, log_message) return func(*args, **kwargs) @attach_wrapper(wrapper) # Attaches "set_level" to "wrapper" as attribute def set_level(new_level): # Function that allows us to set log level nonlocal level level = new_level @attach_wrapper(wrapper) # Attaches "set_message" to "wrapper" as attribute def set_message(new_message): # Function that allows us to set message nonlocal log_message log_message = f"{func.__name__} - {new_message}" return wrapper return decorate # Example Usage @log(logging.WARN, "example-param") def somefunc(args): return args somefunc("some args") somefunc.set_level(logging.CRITICAL) # Change log level by accessing internal decorator function somefunc.set_message("new-message") # Change log message by accessing internal decorator function somefunc("some args") |
有點複雜,這裡的想法是log函式接受引數並將其提供給內部wrapper函式使用。然後,透過新增附加到裝飾器的訪問器函式使這些引數可調整。
至於functools.wraps裝飾器:如果我們在這裡不使用它,函式(func.__name__)的名稱將被裝飾器的名稱覆蓋。但這是一個問題,因為我們要列印名稱。透過functools.wraps將函式名稱,文件字串和引數列表複製到裝飾器函式上,可以解決此問題。
無論如何,這是上面程式碼的輸出。很整潔吧?
2020-05-01 14:42:10,289 - __main__ - WARNING - somefunc - example-param 2020-05-01 14:42:10,289 - __main__ - CRITICAL - somefunc - new-message |
__repr__ 有關更多可讀日誌
對您的程式碼的輕鬆改進使其更易於除錯,這是__repr__在類中新增方法。如果您不熟悉此方法,它所做的就是返回類例項的字串表示形式。__repr__方法的最佳實踐是輸出可用於重新建立例項的文字。例如:
class Circle: def __init__(self, x, y, radius): self.x = x self.y = y self.radius = radius def __repr__(self): return f"Rectangle({self.x}, {self.y}, {self.radius})" ... c = Circle(100, 80, 30) repr(c) # Circle(100, 80, 30) |
如果不希望或不可能像上面那樣顯示物件,那麼很好的選擇是使用<...>,例如使用<_io.TextIOWrapper name='somefile.txt' mode='w' encoding='UTF-8'>。
除了__repr__之外,最好還是實現__str__,這是在print(instance)呼叫時預設使用的方法。使用這兩種方法,您只需列印變數即可獲得很多資訊。
__missing__ 字典的Dunder方法
如果出於某種原因需要實現自定義詞典類,那麼當您嘗試訪問實際上不存在的鍵時,可能會出現來自KeyErrors 一些錯誤。為了避免在程式碼中四處查詢並檢視缺少哪個鍵,可以實現特殊的__missing__方法,該方法在每次KeyError引發時都會呼叫。
class MyDict(dict): def __missing__(self, key): message = f'{key} not present in the dictionary!' logging.warning(message) return message # Or raise some error instead |
上面的實現非常簡單,只返回並記錄缺少鍵的訊息,但是您也可以記錄其他有價值的資訊,以便為您提供更多有關程式碼錯誤的上下文。
除錯崩潰的應用程式
如果您的應用程式崩潰後才有機會檢視其中發生的情況,那麼您可能會發現此技巧非常有用。
使用-i引數(python3 -i app.py)執行該應用程式會使該程式在退出時立即啟動互動式Shell。此時,您可以檢查變數和函式。
如果這還不夠好,可以使用更大的錘子- pdb- Python除錯。pdb具有相當多的功能,這些功能可以保證自己撰寫一篇文章。但這是示例,也是最重要的部分的摘要。首先讓我們看一下崩潰的指令碼:
# crashing_app.py SOME_VAR = 42 class SomeError(Exception): pass def func(): raise SomeError("Something went wrong...") func() |
現在,如果使用-i引數執行它,我們將有機會對其進行除錯:
# Run crashing application ~ $ python3 -i crashing_app.py Traceback (most recent call last): File "crashing_app.py", line 9, in <module> func() File "crashing_app.py", line 7, in func raise SomeError("Something went wrong...") __main__.SomeError: Something went wrong... >>> # We are interactive shell >>> import pdb >>> pdb.pm() # start Post-Mortem debugger > .../crashing_app.py(7)func() -> raise SomeError("Something went wrong...") (Pdb) # Now we are in debugger and can poke around and run some commands: (Pdb) p SOME_VAR # Print value of variable 42 (Pdb) l # List surrounding code we are working with 2 3 class SomeError(Exception): 4 pass 5 6 def func(): 7 -> raise SomeError("Something went wrong...") 8 9 func() <p class="indent">[EOF] (Pdb) # Continue debugging... set breakpoints, step through the code, etc. |
上面的除錯會話非常簡要地顯示了您可以使用的功能pdb。程式終止後,我們進入互動式除錯會話。首先,我們匯入pdb並啟動偵錯程式。在這一點上,我們可以使用所有pdb命令。在上面的示例中,我們使用p命令列印變數,並使用l命令列出程式碼。大多數情況下,您可能希望設定可以使用b LINE_NO的斷點並執行程式,直到命中斷點(c),然後繼續使用逐步執行該功能s,還可以選擇使用來列印stacktrace w。有關命令的完整列表,請轉到pdbdocs。
檢查堆疊跟蹤
假設您的程式碼是例如在遠端伺服器上執行的Flask或Django應用程式,您無法獲得互動式除錯會話。在這種情況下,您可以使用traceback和sys軟體包來更深入地瞭解程式碼失敗的原因:
import traceback import sys def func(): try: raise SomeError("Something went wrong...") except: traceback.print_exc(file=sys.stderr) |
執行後,上面的程式碼將列印最後引發的異常。除了列印例外,您還可以使用traceback包來列印stacktrace(traceback.print_stack())或提取原始堆疊幀,對其進行格式化並進一步檢查(traceback.format_list(traceback.extract_stack()))。
在除錯過程中重新載入模組
有時您可能正在除錯或嘗試使用互動式Shell中的某些功能並對其進行頻繁更改。為了簡化執行/測試和修改的週期,可以執行importlib.reload(module)以避免每次更改後都必須重新啟動互動式會話:
>>> import func from module >>> func() "This is result..." # Make some changes to "func" >>> func() "This is result..." # Outdated result >>> from importlib import reload; reload(module) # Reload "module" after changes made to "func" >>> func() "New result..." |
本技巧更多地是關於效率而不是除錯。能夠跳過一些不必要的步驟,並使您的工作流程更快,更高效,總是很高興的。通常,不時重新載入模組是個好主意,因為它可以幫助您避免嘗試除錯同時修改過很多次的程式碼。
相關文章
- Python語音識別終極指南Python
- Angular CLI 終極指南Angular
- nmap終極使用指南
- Java日誌終極指南Java
- A/B測試終極指南
- ChatGPT的終極指南概要ChatGPT
- CSS居中對齊終極指南CSS
- UI設計終極配色指南UI
- FFmpeg - 終極指南 | IMG.LY
- Linux 日誌終極指南Linux
- Android APP 終極瘦身指南AndroidAPP
- Bug Bounty平臺的終極指南
- [譯]移動API安全終極指南API
- Linux桌面環境終極指南Linux
- WordPress 遷移外掛終極指南
- 資料庫效能提升終極指南資料庫
- 【終極指南】使用Python視覺化分析文字情感傾向Python視覺化
- Python 除錯工具 pudb 的使用指南Python除錯
- Kubernetes部署之終極指南 - semaphoreci
- CUDA 矩陣乘法終極優化指南矩陣優化
- surface安裝linux終極拯救指南Linux
- 區塊鏈學習者終極指南區塊鏈
- 終極自託管解決方案指南
- Mobile Web 除錯指南(2):遠端除錯Web除錯
- windbg的時間旅行實現對 C# 程式的終極除錯C#除錯
- nodejs除錯指南NodeJS除錯
- 創業起步?先收藏這份終極指南創業
- 更快學會任何東西的終極指南
- Electron 應用除錯指南除錯
- Node.js除錯指南Node.js除錯
- GitHub終極指南,教你如何在GitHub中“挖礦”Github
- 3xx HTTP狀態碼的終極指南HTTP
- 扁平化圖示的終極設計指南
- [譯] Go 終極指南:編寫一個 Go 工具Go
- iPhone螢幕解析度終極指南–資訊圖iPhone
- python 除錯Python除錯
- Python 程式碼除錯—使用 pdb 除錯Python除錯
- 【譯Py】資料科學麵試終極指南(七)資料科學