ホームページ >バックエンド開発 >Python チュートリアル >Pythonでデバッグする方法は何ですか?

Pythonでデバッグする方法は何ですか?

王林
王林転載
2023-05-12 20:13:131352ブラウズ

ログは必須です

何らかのログ設定を行わずにアプリケーションを作成すると、最終的に後悔することになります。アプリケーションにログがないと、エラーのトラブルシューティングが困難になります。幸いなことに、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].baseFilenameFind file path):

[12:52:35] {<stdin>:1} ERROR - Some serious error occurred.
[12:52:35] {<stdin>:1} WARNING - Function you are using is deprecated.

このセットアップは (通常の場合と同様) 十分に適切であるように見えますが、構成は適切にフォーマットされています。読みやすいログは作業を楽にしてくれます。構成を改善および拡張する 1 つの方法は、ロガーが読み取る .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 ファイルを直接サポートしませんが、dictionary 構成をサポートします。これは、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関数名、docstring、および引数リストをデコレータ関数にコピーすることで解決できます。

とにかく、これは上記のコードの出力です。かなりきれいですよね?

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 名='somefile.txt' モード='w' エンコーディング='UTF-8'>

__repr__ に加えて、print(instance)# を呼び出すときにデフォルトで使用される __str__ メソッドを実装することもお勧めします。 ## 方法。これら 2 つの方法を使用すると、変数を出力するだけで多くの情報を取得できます。

__missing__dictionary の Dunder メソッド

何らかの理由でカスタム辞書クラスを実装する必要がある場合、実際には存在しないキーにアクセスしようとすると、 ,

KeyErrorによりいくつかのバグが発生する可能性があります。欠落している key をコード内で探す必要を避けるために、KeyError が発生するたびに呼び出される特別な __missing__ メソッドを実装できます。

class MyDict(dict):
    def __missing__(self, key):
        message = f&#39;{key} not present in the dictionary!&#39;
        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.<p data-id="p838747a-jbmZAH6B">上面的调试会话非常简单地展示了如何使用<code>pdb</code>。程序终止后,我们进入交互式调试会话。首先,我们导入<code>pdb</code>并启动调试器。此时,我们可以使用所有<code>pdb</code>命令。作为上面的示例,我们使用<code>p</code>命令打印变量,使用<code>l</code>命令打印列表代码。大多数情况下,你可能希望设置断点,你可以使用<code>b LINE_NO</code>来设置断点,并运行程序,直到达到断点(<code>c</code>),然后继续使用<code>s</code>单步执行函数,也可以使用<code>w</code>打印堆栈轨迹。有关命令的完整列表,你可以转到官方pdb文档。</p>
<h3 id="h6" data-id="h590b8bf-d1npfng7">检查堆栈轨迹</h3>
<p data-id="p124945a-Ln9Ti0YE">例如,假设你的代码是在远程服务器上运行的Flask或Django应用程序,你无法在其中获得交互式调试会话。在这种情况下,你可以使用<code>traceback</code>和<code>sys</code>包来了解代码中的错误:</p>
<pre class="brush:php;toolbar:false">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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。