Python 3 カスタムチェックの移行

Python 3 カスタムチェックの実行をデフォルトでサポートするのは、Agent v7 以降のみです。Python 3 カスタムチェックをネイティブで実行するには、最新バージョンの Agent にアップグレードしてください。Agent をアップグレードせずにカスタムチェックの移行をテストする場合は、Agent v6.14 以降の Python 3 ランタイムを有効化します。

概要

本ガイドには、Python 2 から Python 3 への移行チェックに関する情報とベストプラクティスが記載されています、Datadog のカスタムチェックの互換性 ツールを使用して、ご利用のカスタムチェックについて、Python 3 との互換性や移行の必要性をご確認ください。

複数の Agent バージョンでコードを実行できる柔軟性を提供するため、本ガイドでは下位互換性の保持に重点を置いています。

エディターおよびツール

Pylint

Pylint には カスタムチェックと Python 3 の互換性を検証できる機能が含まれています。

インストール

pip から Python 2 にインストールして開始します。

$ python2 -m pip install pylint

Python 2 インタープリターへのパスが異なる場合は、上記のコマンドで python2 を置換してください。

使用方法

pylint コマンドを実行して、カスタムチェックまたはインテグレーションが Python 3 で実行されるか検証します。CHECK を Python モジュールまたはパッケージフォルダーへの有効なパスと置換します。

$ python2 -m pylint -sn --py3k CHECK

たとえば、以下のとおりです。

$ python2 -m pylint -sn --py3k ~/dev/my-check.py
************* Module my-check
E:  4, 4: print statement used (print-statement)
W:  7,22: Calling a dict.iter*() method (dict-iter-method)
W:  9, 8: division w/o __future__ statement (old-division)

非互換性が解消されると、同じコマンドで何も返されなくなります。

$ python2 -m pylint -sn --py3k ~/dev/my-check.py
$ 

pylint は Python 3 インタープリターのコード実行を妨げる問題を検知するものの、論理的な妥当性は確認できません。コードを変更したら、必ずチェックを実行し、アウトプットを検証してください。

2to3

2to3 により、Python 2 コードを Python 3 コードに変換します。foo.pyという名称のカスタムチェックを使用している場合は 2to3 を実行します。

$ 2to3 foo.py

2to3 を実行すると、元のソースファイルとの差分が表示されます。2to3 の詳細については、公式 2to3 ドキュメントを参照してください。

エディター

最新の IDE およびエディターでは、自動的に高度な lint が実行されます。lint が実行可能な Python 3 を参照していることを確認してください。これにより、Python 2 専用のレガシーファイルを開くと、lint のエラーや警告が PyCharm の横側に色付きのチェックとして表示されるか、Visual Studio Code の底部にクリックできるボックスとして表示されます。

Python の移行

パッケージのインポート

Python3 で Datadog パッケージのネームスペースを標準化するには、すべてのリソースがベースサブパッケージ下に存在している必要があります。例えば、

from datadog_checks.checks import AgentCheck

は次のようになります。

from datadog_checks.base.checks import AgentCheck

Six

Six は、Python 2 と Python3 の双方で機能する Python コードを開発者が使用できるように考えられた、双方に互換性のあるライブラリです。Six を使用して Python 2 のレガシーコードを Python 3 と互換性のあるコードに変換させた例をいくつか以下に示します。

辞書型メソッド

Python 3 では、dict.iterkeys()dict.iteritems()dict.itervalues() の各メソッドを使用できません。

Python 2Python 2 および 3
for key in mydict.iterkeys():
   ...
for key in mydict:
   ...
for key, value in mydict.iteritems():
   ...
from six import iteritems

for key, value in iteritems(mydict):
   ...
for value in mydict.itervalues():
   ...
from six import itervalues

for value in itervalues(mydict):
   ...

また、Python 3では、dict.keys()dict.items()dict.values() の各メソッドはイテレータを返します。そのため、イテレーションの間に辞書を修正する必要がある場合は、先にコピーを作成します。辞書のキー、項目、値をリストとして取得するには、

Python 2Python 2 および 3
mykeylist = mydict.keys()mykeylist = list(mydict)
myitemlist = mydict.items()myitemlist = list(mydict.items())
myvaluelist = mydict.values()myvaluelist = list(mydict.values()

dict.has_key() メソッドは Python 2 で廃止予定となっており、Python 3 では削除されています。代わりに in 演算子を使用してください

Python 2Python 2 および 3
mydict.has_key('foo') //非推奨foo in mydict

標準ライブラリの変更

Python 3 には、再編成された標準ライブラリ機能があります。ここでは、いくつかのモジュールや関数が名称変更または移動されています。Python の両バージョンで、six.moves で移動されたモジュールをインポートできます。

Python 2Python 3Python 2 および 3
import HTMLParserimport html.parserfrom six.moves import html_parser

名称が変更されたモジュールのリストについては、Six ドキュメントを確認してください。: urlliburllib2urlparse の各モジュールは大幅に再編成されています。

Unicode

Python 2 は Unicode テキストとバイナリコード化されたデータを同様に扱い、バイトと文字列間の自動変換を試みます。すべての文字が ASCII であれば問題なく動作しますが、非 ASCII 文字にぶつかると不測の挙動につながります。

typeリテラルPython 2Python 3
バイトb’…'バイナリバイナリ
str‘…’バイナリテキスト
unicodeu’…'テキストテキスト

テキストデータは Unicode コードポイントです。ストレージとトランスミッションは .encode(encoding) を使用してエンコードする必要があります。バイナリデータは、バイトシーケンスとして表現されるエンコードされたコードポイントです。テキストに戻すには .decode(encoding) を使用してデコードする必要があります。ファイルからテキストを読み取る際は、io パッケージの open 関数が便利です。データの読み取りはすでに Unicode にデコードされています。

from io import open

f = open('textfile.txt', encoding='utf-8')
contents = f.read()  # コンテンツは 'utf-8' を使用してユニコードにデコードされます。これは、バイトではありません!

詳細については Ned Batchelder の実用的な Unicode を参照してください。

印刷

Python 3 では、印刷は明確に関数として扱われています。印刷を関数にするには、Python のバージョンに関わらず、古い印刷ステートメントを使用して from __future__ import print_function をファイルの先頭に記述し、括弧を追加して関数コールを実行します。

Python 2Python 2 および 3
print "foo"from __future__ import print_function

print("foo")

整数の除算

Python 2 では / 演算子が整数の切り捨て除算を実行します。

Python 2

>> 5/2
2

Python 3 では、 / 演算子が浮動小数点除算を実行します。// 演算子は切り捨て除算を実行します。

Python 3

>> 5/2
2.5
>> 5//2
2

Python 3 の同じ挙動を再現するには、バージョンに関わらず、from __future__ import division を除算を使用するファイルの先頭に記述し、// を使用して切り捨て除算の結果を導きます。

丸め

Python 2 では、標準ライブラリの丸めメソッドに Round Half Up 法、Python 3 では Round To Even 法を使用します。

Python 2

>> round(2.5)
3
>> round(3.5)
4

Python 3

>> round(2.5)
2
>> round(3.5)
4

Datadog では datadog_checks_base でユーティリティ関数の round_value を提供して、Python 2 と Python 3 の両方で Python 2 の挙動を再現できるようにしています。

例外

Python 3 では except と raise に異なる構文を使用します。

Python 2Python 2 および 3
try:
   ...
except Exception, variable:
   ...
try:
   ...
except Exception as variable:
   ...
raise Exception, argsraise Exception(args)

相対インポート

Python 3 では、ドット (.) 構文を使用して、相対インポートを明示する必要があります。

パッケージが以下のような構造だとします。

mypackage/
    __init__.py
    math.py
    foo.py

また、math.pygcd と呼ばれる関数 (標準ライブラリ math モジュールの gcd 関数とは微妙に異なる機能を含む) が含まれ、標準ライブラリではなく、ローカルパッケージの gcd 関数を使用するとします。

Python 2 では、パッケージ内であれば、このパッケージのモジュールがグローバルパッケージより優先されます。from math import gcd を使用して、gcdmypackage/math.py からインポートします。

Python 3 では、. 以外で始まるインポート形式は絶対インポートとして解釈されます。from math import gcd を使用して標準ライブラリから gcd をインポートします。

Python 2Python 2 および 3
from math import gcdfrom .math import gcd

または、さらに読みやすくするには

Python 2Python 2 および 3
from math import gcdfrom mypackage.math import gcd

イテレータ

Python 2 でリストを返していたいくつかの関数は、Python 3 ではイテレータを返します。mapfilterzip などがこれに該当します。

Python 2 の挙動を保持する最も簡単な解決策は、list へのコールでこれらの関数を括る方法です。

Python 2Python 2 および 3
map(myfunction, myiterable)list(map(myfunction, myiterable))
filter(myfunction, myiterable)list(filter(myfunction, myiterable))
zip(myiterable1, myiterable2)list(zip(myiterable1, myiterable2))

Python 3 では xrange 関数は削除されています。代わりに、range 関数が反復可能な range オブジェクトを返します。from six.moves import rangerange をインポートします。

next メソッドを呼び出す代わりに、組み込み型 next 関数を使用します。例えば、iterator.next()next(iterator) に書き換えます。

その他の参考資料