ホームページ >バックエンド開発 >Python チュートリアル >Pythonでデバッグする方法は何ですか?
何らかのログ設定を行わずにアプリケーションを作成すると、最終的に後悔することになります。アプリケーションにログがないと、エラーのトラブルシューティングが困難になります。幸いなことに、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
Find 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 つの方法を使用すると、変数を出力するだけで多くの情報を取得できます。
dictionary の Dunder メソッド
KeyErrorによりいくつかのバグが発生する可能性があります。欠落している
key をコード内で探す必要を避けるために、KeyError が発生するたびに呼び出される特別な
__missing__ メソッドを実装できます。
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.<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 サイトの他の関連記事を参照してください。