Pythonデバッグの究極のガイド
27183 ワード
注:これは、もともとは11で投稿されましたmartinheinz.dev
たとえあなたが明確で読みやすいコードを書いたとしても、たとえあなたが非常に経験を積んだ開発者であっても、あなたのコードをテストでカバーしても、奇妙なバグは必然的に現れ、何らかの方法でそれらをデバッグする必要があります.多くの人々のリゾートだけの束を使用して
ログは必須です
いくつかの並べ替えのセットアップなしでアプリケーションを書く場合は、最終的にそれを後悔するようになります.あなたのアプリケーションから任意のログを持っていないことは非常に困難なバグをトラブルシューティングすることができます.幸運にも- Pythonでは、基本的なロガーの設定はとても簡単です.
これらの設定フィールドがどこから来たのか疑問に思っているなら、これらは文書化されますhere それらのほとんどは最初の例に示すようなキーワード引数です.
それで、現在ファイルで設定を持っていることは、ロードする必要があることを意味します.YAMLファイルで最も簡単な方法
ログ作成者
前のログのヒントを続けて、いくつかのバギー関数のログコールが必要な状況になる可能性があります.特定のログレベルとオプションのメッセージですべての関数呼び出しを記録するログデコレータを使用することができます.デコレータを見ましょう
とにかく、これは上のコードの出力です.きれいなきちんとした?
よりデバッグ可能にするコードへの容易な改善は
離れて
何らかの理由でカスタム辞書クラスを実装する必要がある場合は
アプリケーションのクラッシュデバッグ
あなたのアプリケーションがクラッシュする前に何が起こっているかを確認するチャンスを得る場合は、このトリックは非常に有用かもしれない.
アプリケーションの実行
それが十分でないならば、あなたはより大きなハンマーを持ってくることができます
トレーストレースの検査
例えば、あなたのコードが対話的なデバッグセッションを得ることができないリモートサーバーで実行しているフラスコまたはdjangoアプリケーションであると言いましょう.その場合には
デバッグ中のモジュールの再読み込み
場合によっては、デバッグや実験的なシェルでいくつかの機能を試して、頻繁に変更を行うことがあります.実行/テストのサイクルを簡単に変更するには、実行することができます
結論
ほとんどの時間、実際に何のプログラミングは-試行錯誤の多くです.一方、私の意見では-私の意見では-芸術とそれに良い時間と経験がかかります-あなたが使用するライブラリやフレームワークを知っているほど、簡単に取得します.上記のヒントとトリックは、デバッグを少し効率的で高速にすることができますが、これらのPython特有のツールを別にすれば、デバッグへの一般的なアプローチを理解したいかもしれませんThe Art of Debugging レミーシャープによって.
たとえあなたが明確で読みやすいコードを書いたとしても、たとえあなたが非常に経験を積んだ開発者であっても、あなたのコードをテストでカバーしても、奇妙なバグは必然的に現れ、何らかの方法でそれらをデバッグする必要があります.多くの人々のリゾートだけの束を使用して
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.
[12:52:35] {<stdin>:1} WARNING - Function you are using is deprecated.
このセットアップは、それが十分に良いように見えるかもしれません(そして、しばしば)、しかし、よく構成されて、フォーマットされて、読み込み可能なログを持つことはあなたの人生をとても簡単にすることができます.設定を改良し拡張する一つの方法は.ini
or .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ファイルで物事を維持することははるかに簡単にセットアップし、上記のような非常に特定の設定で複数のロガーを微調整します.これらの設定フィールドがどこから来たのか疑問に思っているなら、これらは文書化されますhere それらのほとんどは最初の例に示すようなキーワード引数です.
それで、現在ファイルで設定を持っていることは、ロードする必要があることを意味します.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を使用して簡単に作成できる辞書の設定をサポートしていますyaml.safe_load
. あなたが古い使用する傾向があるならば.ini
ファイルは、私はちょうど辞書の設定を使用してdocs . より多くの例logging cookbook .ログ作成者
前のログのヒントを続けて、いくつかのバギー関数のログコールが必要な状況になる可能性があります.特定のログレベルとオプションのメッセージですべての関数呼び出しを記録するログデコレータを使用することができます.デコレータを見ましょう
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 name='somefile.txt' mode='w' encoding='UTF-8'>
.離れて
__repr__
, また、実装する良いアイデアだ__str__
時に使用するメソッドprint(instance)
が呼ばれる.これらの2つの方法で、あなたの変数を印刷するだけで、たくさんの情報を得ることができます.__missing__
辞書のdunderメソッド何らかの理由でカスタム辞書クラスを実装する必要がある場合は
KeyError
あなたが実際に存在しないいくつかのキーにアクセスしようとするときs.コードの中でpokeすることを避けるために、どのキーが不足しているかを参照してください__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
) プログラムが終了するとすぐに、それは対話的なシェルを開始します.その時点で変数と関数を検査できます.それが十分でないならば、あなたはより大きなハンマーを持ってくることができます
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
, オプションで印刷可能なStackTracew
. コマンドの完全なリストについては pdb
docs .トレーストレースの検査
例えば、あなたのコードが対話的なデバッグセッションを得ることができないリモートサーバーで実行しているフラスコまたはdjangoアプリケーションであると言いましょう.その場合には
traceback
and sys
あなたのコードで失敗していることについてのより多くの洞察を得るパッケージimport traceback
import sys
def func():
try:
raise SomeError("Something went wrong...")
except:
traceback.print_exc(file=sys.stderr)
Runの場合、上記のコードが送出された最後の例外を出力します.例外を印刷することは別として、使用することもできますtraceback
StackTraceを印刷するパッケージtraceback.print_stack()
) または生のスタックフレームを取り出し、それをフォーマットし、さらに検査します(traceback.format_list(traceback.extract_stack())
).デバッグ中のモジュールの再読み込み
場合によっては、デバッグや実験的なシェルでいくつかの機能を試して、頻繁に変更を行うことがあります.実行/テストのサイクルを簡単に変更するには、実行することができます
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..."
このチップは、デバッグより効率的です.それは常にいくつかの不必要な手順をスキップして、ワークフローをより速く、より効率的にすることができるように素晴らしいです.一般に、モジュールを再読み込みするのは良いアイデアです.Debugging is an Art.
結論
ほとんどの時間、実際に何のプログラミングは-試行錯誤の多くです.一方、私の意見では-私の意見では-芸術とそれに良い時間と経験がかかります-あなたが使用するライブラリやフレームワークを知っているほど、簡単に取得します.上記のヒントとトリックは、デバッグを少し効率的で高速にすることができますが、これらのPython特有のツールを別にすれば、デバッグへの一般的なアプローチを理解したいかもしれませんThe Art of Debugging レミーシャープによって.
Reference
この問題について(Pythonデバッグの究極のガイド), 我々は、より多くの情報をここで見つけました https://dev.to/martinheinz/ultimate-guide-to-python-debugging-1739テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol