首頁 > 後端開發 > Python教學 > Python調試的方法是什麼

Python調試的方法是什麼

王林
發布: 2023-05-12 20:13:13
轉載
1358 人瀏覽過

記錄是必須的

如果你編寫應用程式時沒有某種日誌設置,你最終會後悔。如果應用程式中沒有任何日誌,則很難排除任何錯誤。幸運的是,在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.
[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 logger實際上並不直接支援YAML文件,但它支援字典配置,可以使用yaml.safe_load從YAML輕鬆建立字典設定。如果你傾向於使用舊的.ini文件,那麼我只想指出,根據官方文檔,使用字典配置是新應用程式的建議方法。有關更多範例,請查看官方日誌記錄手冊。

日誌裝飾器

繼續前面的日誌記錄技巧,你可能會遇到需要記錄一些錯誤函數呼叫的情況。你可以使用日誌裝飾器來代替修改所述函數的主體,該裝飾器將使用特定的日誌等級和可選訊息記錄每個函數呼叫。讓我們看看裝飾器:

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方法

如果你出於任何原因需要實作自訂字典類,那麼當你嘗試存取一些實際上不存在的鍵時,可能會因為KeyError而出現一些bug。為了避免必須在程式碼中四處查看缺少的,可以實作特殊的__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
登入後複製

上面的實作非常簡單,只回傳並​​記錄帶有遺失key的訊息,但你也可以記錄其他有價值的信息,以便為你提供有關程式碼中出現錯誤的更多上下文。

调试崩溃的应用程序

如果你的应用程序在你有机会看到其中发生了什么之前崩溃,你可能会发现这个技巧非常有用。

-i使用参数-i ( python3 -i app.py)运行应用程序会导致它在程序退出后立即启动交互式 shell。此时你可以检查变量和函数。

如果这还不够好,可以使用更大的hammer-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()
[EOF]
(Pdb)  # Continue debugging... set breakpoints, step through the code, etc.
登入後複製

上面的调试会话非常简单地展示了如何使用pdb。程序终止后,我们进入交互式调试会话。首先,我们导入pdb并启动调试器。此时,我们可以使用所有pdb命令。作为上面的示例,我们使用p命令打印变量,使用l命令打印列表代码。大多数情况下,你可能希望设置断点,你可以使用b LINE_NO来设置断点,并运行程序,直到达到断点(c),然后继续使用s单步执行函数,也可以使用w打印堆栈轨迹。有关命令的完整列表,你可以转到官方pdb文档。

检查堆栈轨迹

例如,假设你的代码是在远程服务器上运行的Flask或Django应用程序,你无法在其中获得交互式调试会话。在这种情况下,你可以使用tracebacksys包来了解代码中的错误:

import traceback
import sys

def func():
    try:
        raise SomeError("Something went wrong...")
    except:
        traceback.print_exc(file=sys.stderr)
登入後複製

运行时,上面的代码将打印引发的最后一个异常。除了打印例外,你还可以使用traceback包打印堆栈轨迹(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調試的方法是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板