ロギングクックブック
- 著者
- ビナイサジップ
このページには、過去に役立つことがわかっているロギングに関連するレシピがいくつか含まれています。
複数のモジュールへのログインの使用
logging.getLogger('someLogger')
を複数回呼び出すと、同じロガーオブジェクトへの参照が返されます。 これは、同じモジュール内だけでなく、同じPythonインタープリタープロセス内にある限り、モジュール間でも当てはまります。 同じオブジェクトへの参照にも当てはまります。 さらに、アプリケーションコードは、1つのモジュールで親ロガーを定義および構成し、別のモジュールで子ロガーを作成(構成はしない)することができ、子へのすべてのロガー呼び出しは親に渡されます。 メインモジュールは次のとおりです。
補助モジュールは次のとおりです。
出力は次のようになります。
複数のスレッドからのロギング
複数のスレッドからのロギングには、特別な労力は必要ありません。 次の例は、メイン(initIal)スレッドと別のスレッドからのロギングを示しています。
実行すると、スクリプトは次のように出力されます。
これは、予想どおりにログ出力が散在していることを示しています。 もちろん、このアプローチは、ここに示されているよりも多くのスレッドで機能します。
複数のハンドラーとフォーマッター
ロガーはプレーンなPythonオブジェクトです。 addHandler()メソッドには、追加できるハンドラーの数に対する最小または最大のクォータがありません。 アプリケーションがすべての重大度のすべてのメッセージをテキストファイルに記録すると同時に、エラー以上をコンソールに記録すると便利な場合があります。 これを設定するには、適切なハンドラーを構成するだけです。 アプリケーションコードのロギング呼び出しは変更されません。 これは、前の単純なモジュールベースの構成例へのわずかな変更です。
'application'コードは複数のハンドラーを気にしないことに注意してください。 変更されたのは、 fh という名前の新しいハンドラーの追加と構成だけでした。
重大度の高いまたは低いフィルターを使用して新しいハンドラーを作成する機能は、アプリケーションを作成およびテストするときに非常に役立ちます。 デバッグに多くのprint
ステートメントを使用する代わりに、logger.debug
を使用します。後で削除またはコメントアウトする必要があるprintステートメントとは異なり、logger.debugステートメントはソースコードにそのまま残すことができます。再び必要になるまで休眠状態を保ちます。 その時点で発生する必要がある唯一の変更は、デバッグするロガーやハンドラーの重大度レベルを変更することです。
複数の宛先へのロギング
さまざまなメッセージ形式でさまざまな状況でコンソールとファイルにログを記録するとします。 レベルがDEBUG以上のメッセージをファイルに記録し、レベルがINFO以上のメッセージをコンソールに記録するとします。 また、ファイルにはタイムスタンプが含まれている必要がありますが、コンソールメッセージには含まれていないと仮定しましょう。 これを実現する方法は次のとおりです。
これを実行すると、コンソールに表示されます
ファイルには次のようなものが表示されます
ご覧のとおり、DEBUGメッセージはファイルにのみ表示されます。 他のメッセージは両方の宛先に送信されます。
この例では、コンソールハンドラーとファイルハンドラーを使用していますが、選択したハンドラーの数と組み合わせを任意に使用できます。
構成サーバーの例
ロギング構成サーバーを使用するモジュールの例を次に示します。
そして、これがファイル名を受け取り、そのファイルをサーバーに送信するスクリプトです。新しいロギング構成として、適切にバイナリエンコードされた長さが前に付けられます。
ネットワークを介したロギングイベントの送受信
ネットワークを介してロギングイベントを送信し、受信側でそれらを処理するとします。 これを行う簡単な方法は、SocketHandler
インスタンスを送信側のルートロガーにアタッチすることです。
受信側では、 SocketServer モジュールを使用して受信機をセットアップできます。 基本的な作業例を次に示します。
最初にサーバーを実行し、次にクライアントを実行します。 クライアント側では、コンソールには何も印刷されません。 サーバー側では、次のように表示されます。
一部のシナリオでは、pickleにセキュリティ上の問題があることに注意してください。 これらが影響する場合は、 makePickle()メソッドをオーバーライドしてそこに代替を実装し、上記のスクリプトを代替のシリアル化を使用するように適合させることで、代替のシリアル化スキームを使用できます。
ログ出力にコンテキスト情報を追加する
ロギング呼び出しに渡されるパラメーターに加えて、ロギング出力にコンテキスト情報を含めたい場合があります。 たとえば、ネットワークアプリケーションでは、クライアント固有の情報をログに記録することが望ましい場合があります(たとえば、 リモートクライアントのユーザー名、またはIPアドレス)。 extra パラメーターを使用してこれを実現することもできますが、この方法で情報を渡すことが常に便利であるとは限りません。 接続ごとに Logger インスタンスを作成したくなるかもしれませんが、これらのインスタンスはガベージコレクションされていないため、これはお勧めできません。 これは実際には問題ではありませんが、 Logger インスタンスの数が、アプリケーションのロギングで使用する粒度のレベルに依存している場合、の数が管理しにくい場合があります。 Logger インスタンスは事実上無制限になります。
LoggerAdaptersを使用してコンテキスト情報を伝達する
ロギングイベント情報とともに出力されるコンテキスト情報を渡すことができる簡単な方法は、 LoggerAdapter クラスを使用することです。 このクラスは Logger のように設計されているため、 debug()、 info()、 warning()を呼び出すことができます。 、 error()、 exception()、 critical()および log()。 これらのメソッドは、 Logger の対応するメソッドと同じ署名を持っているため、2つのタイプのインスタンスを同じ意味で使用できます。
LoggerAdapter のインスタンスを作成するときは、 Logger インスタンスと、コンテキスト情報を含むdictのようなオブジェクトを渡します。 LoggerAdapter のインスタンスでロギングメソッドの1つを呼び出すと、コンストラクターに渡された Logger の基になるインスタンスに呼び出しが委任され、コンテキスト情報が委任された呼び出し。 LoggerAdapter のコードからの抜粋は次のとおりです。
LoggerAdapter の process()メソッドは、コンテキスト情報がロギング出力に追加される場所です。 ロギング呼び出しのメッセージとキーワードの引数が渡され、基になるロガーへの呼び出しで使用するために、これらの(潜在的に)変更されたバージョンが返されます。 このメソッドのデフォルトの実装では、メッセージはそのままになりますが、コンストラクターに渡されるdictのようなオブジェクトを値とするキーワード引数に「extra」キーが挿入されます。 もちろん、アダプターの呼び出しで「extra」キーワード引数を渡した場合は、サイレントに上書きされます。
'extra'を使用する利点は、dictのようなオブジェクトの値が LogRecord インスタンスの__dict__にマージされ、 Formatter インスタンスでカスタマイズされた文字列を使用できることです。 dictのようなオブジェクトのキー。 別の方法が必要な場合、例えば メッセージ文字列にコンテキスト情報を追加または追加する場合は、 LoggerAdapter をサブクラス化し、 process()をオーバーライドして必要な処理を実行する必要があります。 簡単な例を次に示します。
あなたはこのように使うことができます:
次に、アダプタにログを記録するイベントには、ログメッセージの前にsome_conn_id
の値が付加されます。
dict以外のオブジェクトを使用してコンテキスト情報を渡す
実際のdictを LoggerAdapter に渡す必要はありません。__getitem__
と__iter__
を実装するクラスのインスタンスを渡して、dictのように見せることができます。ロギングに。 これは、値を動的に生成する場合に役立ちます(dictの値は一定です)。
フィルタを使用してコンテキスト情報を伝達する
ユーザー定義の Filter を使用して、コンテキスト情報をログ出力に追加することもできます。 Filter
インスタンスは、渡されたLogRecords
を変更できます。これには、適切なフォーマット文字列を使用して出力できる属性を追加したり、必要に応じてカスタム Formatter を追加したりできます。
たとえば、Webアプリケーションでは、処理中の要求(または少なくともその興味深い部分)をthreadlocal( threading.local )変数に格納し、 [からアクセスできます。 X202X]たとえば、 [のように属性名「ip」と「user」を使用して、リクエストからの情報(たとえば、リモートIPアドレスとリモートユーザーのユーザー名)をLogRecord
に追加します。 X382X]上記の例。 その場合、同じフォーマット文字列を使用して、上記と同様の出力を取得できます。 スクリプトの例を次に示します。
これを実行すると、次のようなものが生成されます。
複数のプロセスから単一のファイルにログを記録する
ロギングはスレッドセーフであり、単一プロセスの複数のスレッドから単一ファイルへのロギングはサポートされていますが、複数プロセスから単一ファイルへのロギングはではありません ]サポートされています。これは、Pythonの複数のプロセス間で単一のファイルへのアクセスをシリアル化する標準的な方法がないためです。 複数のプロセスから単一のファイルにログを記録する必要がある場合、これを行う1つの方法は、すべてのプロセスを SocketHandler にログに記録し、ソケットから読み取るソケットサーバーを実装する別のプロセスを用意することです。そしてファイルに記録します。 (必要に応じて、既存のプロセスの1つに1つのスレッドを割り当てて、この機能を実行できます。)このセクションでは、このアプローチについて詳しく説明し、開始点として使用できる動作中のソケットレシーバーが含まれています。あなたがあなた自身のアプリケーションに適応するために。
マルチプロセッシングモジュールを含む最新バージョンのPythonを使用している場合は、このモジュールの Lock クラスを使用して、プロセスからファイルへのアクセスをシリアル化する独自のハンドラーを作成できます。 。 既存の FileHandler およびサブクラスは、現在マルチプロセッシングを使用していませんが、将来使用する可能性があります。 現在、マルチプロセッシングモジュールは、すべてのプラットフォームで機能するロック機能を提供しているわけではないことに注意してください( https://bugs.python.org/issue3770 を参照)。
ファイルローテーションの使用
ログファイルを特定のサイズに拡大してから、新しいファイルを開いてそれにログを記録したい場合があります。 これらのファイルを一定数保持することをお勧めします。その数のファイルが作成されたら、ファイルの数とサイズの両方が制限されるようにファイルをローテーションします。 この使用パターンの場合、ロギングパッケージは RotatingFileHandler を提供します。
結果は6つの個別のファイルになり、それぞれにアプリケーションのログ履歴の一部が含まれます。
最新のファイルは常にlogging_rotatingfile_example.out
であり、サイズ制限に達するたびに、サフィックス.1
で名前が変更されます。 既存の各バックアップファイルの名前を変更してサフィックスをインクリメントし(.1
は.2
などになります)、.6
ファイルは消去されます。
明らかに、この例では、極端な例としてログの長さを非常に小さく設定しています。 maxBytes を適切な値に設定することをお勧めします。
辞書ベースの構成の例
以下は、ロギング構成ディクショナリの例です。これは、Djangoプロジェクトのドキュメントから抜粋したものです。 このディクショナリは dictConfig()に渡され、構成が有効になります。
この構成の詳細については、Djangoドキュメントの関連セクションを参照してください。
SysLogHandlerに送信されるメッセージへのBOMの挿入
RFC 5424 では、Unicodeメッセージを次の構造を持つバイトのセットとしてsyslogデーモンに送信する必要があります。オプションのpure-ASCIIコンポーネントと、それに続くUTF-8バイトオーダーマーク(BOM)、続いて、UTF-8を使用してエンコードされたUnicode。 (仕様の関連セクションを参照してください。)
Python 2.6および2.7では、メッセージにBOMを挿入するコードが SysLogHandler に追加されましたが、残念ながら、BOMがメッセージの先頭に表示されるため、純粋なものが許可されないため、正しく実装されていませんでした。その前に表示されるASCIIコンポーネント。
この動作が壊れているため、Python2.7.4以降から誤ったBOM挿入コードが削除されています。 ただし、これは置き換えられていません。BOM、その前にオプションの純粋なASCIIシーケンス、その後に任意のUnicodeを含み、UTF-8を使用してエンコードされた、RFC 5424準拠のメッセージを生成する場合は、次のことを行う必要があります。続く:
Formatter インスタンスを SysLogHandler インスタンスに、次のようなフォーマット文字列でアタッチします。
Unicodeコードポイント
u'\ufeff'
は、UTF-8を使用してエンコードされると、UTF-8 BOM(バイト文字列'\xef\xbb\xbf'
)としてエンコードされます。ASCIIセクションを任意のプレースホルダーに置き換えますが、置換後にそこに表示されるデータが常にASCIIであることを確認してください(そうすると、UTF-8エンコード後も変更されません)。
Unicodeセクションを任意のプレースホルダーに置き換えます。 置換後に表示されるデータにASCII範囲外の文字が含まれている場合は、問題ありません。UTF-8を使用してエンコードされます。
フォーマットされたメッセージがUnicodeの場合、はSysLogHandler
によるUTF-8エンコーディングを使用してエンコードされます。 上記のルールに従うと、RFC5424準拠のメッセージを生成できるはずです。 そうしないと、ロギングは文句を言わないかもしれませんが、メッセージはRFC 5424に準拠せず、syslogデーモンが文句を言うかもしれません。
構造化ロギングの実装
ほとんどのロギングメッセージは人間が読むことを目的としているため、機械で簡単に解析することはできませんが、プログラムで解析できる構造化された形式でメッセージを出力したい場合があります(ログメッセージを解析するために複雑な正規表現が必要です)。 これは、ロギングパッケージを使用して簡単に実現できます。 これを実現する方法はいくつかありますが、以下はJSONを使用してマシンで解析可能な方法でイベントをシリアル化する簡単なアプローチです。
上記のスクリプトを実行すると、次のように出力されます。
使用するPythonのバージョンによって、アイテムの順序が異なる場合があることに注意してください。
より特殊な処理が必要な場合は、次の完全な例のように、カスタムJSONエンコーダーを使用できます。
上記のスクリプトを実行すると、次のように出力されます。
使用するPythonのバージョンによって、アイテムの順序が異なる場合があることに注意してください。
dictConfig()を使用したハンドラーのカスタマイズ
ロギングハンドラーを特定の方法でカスタマイズしたい場合があります。 dictConfig()を使用すると、サブクラス化せずにこれを実行できる場合があります。 例として、ログファイルの所有権を設定したい場合があると考えてください。 POSIXでは、これは os.chown()を使用して簡単に実行できますが、stdlibのファイルハンドラーは組み込みのサポートを提供していません。 次のような単純な関数を使用して、ハンドラーの作成をカスタマイズできます。
次に、 dictConfig()に渡されるロギング構成で、次の関数を呼び出すことによってロギングハンドラーを作成するように指定できます。
この例では、説明のために、pulse
ユーザーとグループを使用して所有権を設定しています。 それをまとめて動作するスクリプトchowntest.py
:
これを実行するには、おそらくroot
として実行する必要があります。
$ sudo python3.3 chowntest.py
$ cat chowntest.log
2013-11-05 09:34:51,128 DEBUG mylogger A debug message
$ ls -l chowntest.log
-rw-r--r-- 1 pulse pulse 55 2013-11-05 09:34 chowntest.log
この例ではPython3.3を使用していることに注意してください。これは、shutil.chown()
が表示される場所だからです。 このアプローチは、 dictConfig()をサポートするすべてのPythonバージョン、つまりPython 2.7、3.2以降で機能するはずです。 3.3より前のバージョンでは、たとえばを使用して実際の所有権の変更を実装する必要があります。 os.chown()。
実際には、ハンドラー作成関数は、プロジェクトのどこかにあるユーティリティモジュールにある場合があります。 構成の行の代わりに:
たとえば、次のように使用できます。
ここで、project.util
は、関数が存在するパッケージの実際の名前に置き換えることができます。 上記の動作スクリプトでは、'ext://__main__.owned_file_handler'
を使用すると動作するはずです。 ここで、実際の呼び出し可能オブジェクトは、ext://
仕様の dictConfig()によって解決されます。
この例は、他の種類のファイル変更を実装する方法も示していることを願っています。 特定のPOSIX許可ビットを設定する-同じ方法で、 os.chmod()を使用します。
もちろん、このアプローチは、 FileHandler 以外のタイプのハンドラー(たとえば、回転するファイルハンドラーの1つ、またはまったく別のタイプのハンドラー)に拡張することもできます。
dictConfig()を使用したフィルターの構成
は dictConfig()を使用してフィルターを構成できますが、その方法は一見して明らかではないかもしれません(したがってこのレシピ)。 Filter は標準ライブラリに含まれる唯一のフィルタークラスであり、多くの要件に対応する可能性は低いため(基本クラスとしてのみ存在します)、通常は独自の Filterを定義する必要があります。 サブクラスとオーバーライドされた filter()メソッド。 これを行うには、フィルターの構成ディクショナリで()
キーを指定し、フィルターの作成に使用される呼び出し可能オブジェクトを指定します(クラスが最も明白ですが、[ X227X] Filter インスタンス)。 完全な例を次に示します。
この例は、インスタンスを構成するcallableに構成データをキーワードパラメーターの形式で渡す方法を示しています。 実行すると、上記のスクリプトが出力されます。
これは、フィルターが構成どおりに機能していることを示しています。
注意すべきいくつかの追加ポイント:
- 構成で呼び出し可能オブジェクトを直接参照できない場合(例: 別のモジュールにあり、構成ディクショナリがある場所に直接インポートできない場合は、外部オブジェクトへのアクセスの説明に従って
ext://...
の形式を使用できます。 たとえば、上記の例では、MyFilter
の代わりにテキスト'ext://__main__.MyFilter'
を使用できます。 - フィルタの場合と同様に、この手法を使用してカスタムハンドラとフォーマッタを構成することもできます。 ロギングが構成でのユーザー定義オブジェクトの使用をサポートする方法の詳細については、ユーザー定義オブジェクトを参照してください。また、上記の他のクックブックレシピ dictConfig()を使用したハンドラーのカスタマイズを参照してください。
カスタマイズされた例外フォーマット
カスタマイズされた例外フォーマットを実行したい場合があります-引数のために、例外情報が存在する場合でも、ログに記録されたイベントごとに正確に1行が必要だとします。 次の例に示すように、カスタムフォーマッタクラスを使用してこれを行うことができます。
実行すると、正確に2行のファイルが生成されます。
上記の処理は単純ですが、例外情報を好みに合わせてフォーマットする方法を示しています。 traceback モジュールは、より専門的なニーズに役立つ場合があります。
ロギングメッセージを話す
ロギングメッセージを可視形式ではなく可聴形式でレンダリングすることが望ましい場合があります。 これは、Pythonバインディングがなくても、システムでテキスト読み上げ(TTS)機能を使用できる場合は簡単に実行できます。 ほとんどのTTSシステムには、実行できるコマンドラインプログラムがあり、これはサブプロセスを使用してハンドラーから呼び出すことができます。 ここでは、TTSコマンドラインプログラムがユーザーとの対話や完了までに長い時間がかかることを想定しておらず、ログに記録されるメッセージの頻度がユーザーをメッセージでいっぱいにするほど高くはなく、メッセージは同時にではなく一度に1つずつ話されます。以下の実装例では、次のメッセージが処理される前に1つのメッセージが話されるのを待ちます。これにより、他のハンドラーが待機し続ける可能性があります。 これは、espeak
TTSパッケージが利用可能であることを前提としたアプローチを示す短い例です。
実行すると、このスクリプトは女性の声で「こんにちは」、次に「さようなら」と言うはずです。
もちろん、上記のアプローチは、他のTTSシステム、さらにはコマンドラインから実行される外部プログラムを介してメッセージを処理できる他のシステムにも適用できます。
ロギングメッセージをバッファリングし、条件付きで出力する
一時的な領域にメッセージを記録し、特定の条件が発生した場合にのみメッセージを出力したい場合があります。 たとえば、関数でデバッグイベントのログ記録を開始し、関数がエラーなしで完了した場合、収集したデバッグ情報でログを乱雑にしたくないが、エラーがある場合は、すべてのデバッグが必要な場合があります。出力する情報とエラー。
これは、ロギングをこのように動作させたい関数のデコレータを使用してこれを行う方法を示す例です。 logging.handlers.MemoryHandler を利用します。これにより、何らかの条件が発生するまでログに記録されたイベントをバッファリングできます。条件が発生すると、バッファリングされたイベントはflushed
-別のハンドラー([X212X ] ハンドラー)処理用。 デフォルトでは、MemoryHandler
は、バッファーがいっぱいになるか、レベルが指定されたしきい値以上のイベントが表示されるとフラッシュされます。 カスタムフラッシュ動作が必要な場合は、MemoryHandler
のより特殊なサブクラスでこのレシピを使用できます。
サンプルスクリプトには、foo
という単純な関数があります。この関数は、すべてのログレベルを循環し、sys.stderr
に書き込み、ログを記録しようとしているレベルを示し、実際にそのレベルでメッセージをログに記録します。レベル。 パラメータをfoo
に渡すことができます。これは、trueの場合、ERRORおよびCRITICALレベルでログに記録されます。それ以外の場合は、DEBUG、INFO、およびWARNINGレベルでのみログに記録されます。
スクリプトは、必要な条件付きロギングを実行するデコレータでfoo
をデコレートするように調整するだけです。 デコレータはロガーをパラメータとして受け取り、デコレートされた関数の呼び出し中にメモリハンドラをアタッチします。 デコレータは、ターゲットハンドラ、フラッシュが発生するレベル、およびバッファの容量を使用して、さらにパラメータ化できます。 これらはデフォルトで StreamHandler になり、それぞれsys.stderr
、logging.ERROR
、および100
に書き込みます。
スクリプトは次のとおりです。
このスクリプトを実行すると、次の出力が表示されます。
ご覧のとおり、実際のログ出力は、重大度がERROR以上のイベントがログに記録された場合にのみ発生しますが、その場合、重大度の低い以前のイベントもログに記録されます。
もちろん、従来の装飾手段を使用することもできます。
構成によるUTC(GMT)を使用した時間のフォーマット
UTCを使用して時刻をフォーマットしたい場合があります。これは、以下に示す UTCFormatter などのクラスを使用して実行できます。
その後、 Formatter の代わりにUTCFormatter
をコードで使用できます。 構成を介してこれを行う場合は、 dictConfig() APIを使用して、次の完全な例で示すアプローチを使用できます。
このスクリプトを実行すると、次のように出力されます。
ハンドラーごとに1つずつ、現地時間とUTCの両方として時刻がどのようにフォーマットされるかを示します。
選択的ロギングのためのコンテキストマネージャーの使用
ロギング構成を一時的に変更し、何かを行った後に元に戻すと便利な場合があります。 このため、コンテキストマネージャは、ロギングコンテキストを保存および復元する最も明白な方法です。 このようなコンテキストマネージャーの簡単な例を次に示します。これにより、オプションでログレベルを変更し、純粋にコンテキストマネージャーのスコープ内にログハンドラーを追加できます。
レベル値を指定すると、ロガーのレベルは、コンテキストマネージャーがカバーするwithブロックのスコープ内でその値に設定されます。 ハンドラーを指定すると、ブロックに入るときにロガーに追加され、ブロックから出るときに削除されます。 ブロック終了時にハンドラーを閉じるようにマネージャーに依頼することもできます。ハンドラーが不要になった場合は、これを行うことができます。
それがどのように機能するかを説明するために、上記に次のコードブロックを追加できます。
最初にロガーのレベルをINFO
に設定したため、メッセージ#1は表示され、メッセージ#2は表示されません。 次に、次のwith
ブロックで一時的にレベルをDEBUG
に変更すると、メッセージ#3が表示されます。 ブロックが終了すると、ロガーのレベルがINFO
に復元されるため、メッセージ#4は表示されません。 次のwith
ブロックでは、レベルをDEBUG
に再度設定しますが、sys.stdout
に書き込むハンドラーも追加します。 したがって、メッセージ#5はコンソールに2回表示されます(1回はstderr
経由、もう1回はstdout
経由)。 with
ステートメントの完了後、ステータスは以前と同じであるため、メッセージ#6は表示されますが(メッセージ#1のように)、メッセージ#7は表示されません(メッセージ#2のように)。
結果のスクリプトを実行すると、結果は次のようになります。
$ python logctx.py
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.
もう一度実行しても、stderr
を/dev/null
にパイプすると、stdout
に書き込まれる唯一のメッセージである次のメッセージが表示されます。
$ python logctx.py 2>/dev/null
5. This should appear twice - once on stderr and once on stdout.
繰り返しますが、stdout
を/dev/null
にパイプすると、次のようになります。
$ python logctx.py >/dev/null
1. This should appear just once on stderr.
3. This should appear once on stderr.
5. This should appear twice - once on stderr and once on stdout.
6. This should appear just once on stderr.
この場合、stdout
に出力されたメッセージ#5は期待通りに表示されません。
もちろん、ここで説明するアプローチは、たとえばロギングフィルターを一時的にアタッチするなど、一般化することができます。 上記のコードはPython2とPython3で機能することに注意してください。