2. How to port Python 2 Code to Python 3

Pythonの公式ドキュメントページ中に How to portPython Code to Python3 があります。 "Japanese" タグを選択することで(未翻訳の部分も多いのですが)日本語版も閲覧できます。 ここには、著者が重要と思うこの文書の一部を抜粋して超訳したものを掲載します。

2.1. 短い説明

一つのソースコードをPython 2/3の両方に対応させるための基本的な手順は以下のようになります。:

  1. Python 2.7 だけをサポートすることに気を配ってください。

  2. 十分なカバレージを持つテストがあることを確認しましょう。(coverage.py が助けになります; python -m pip install coverage) (coverage の利用には pytest/unittest/nosetest などのtestプログラムが用意されていることが前提になるようです)

  3. Pytho2 と 3の違いを学びましょう。

  4. コードのアップデートに Futurize (あるいは Modernize ) を使いましょう。(例えば、 python -m pip install future でインストール) [1]

  5. Pylint を使い、あなたのプログラムのPython3 サポートが後戻りしないようにしましょう。 (python -m pip install pylint)

  6. どのライブラリへの依存性がpython3への移行を妨げているかを確認するために、 caniusepython3 を使いましょう。 (python -m pip install caniusepython3)

  7. ライブラリの依存性が解決されたら、継続的な統合手法(continuous integration、CI)を使い Python2および3との互換性を維持していることを確認しましょう。(tox は複数のバージョンのPythonでのテストを助けてくれます。 python -m pip install tox)

  8. あなたのブログラムでのデータ型の使い方がPython2/3の両方で動作することを確認するために、 静的な型検査 を使うことを検討しましょう。(Python2/3の双方でのデータ型の利用状況を検査するのに mypy を使いましょう。python -m pip install mypy).

注釈

注釈: python -m pip install を使うことで、 pip を実行するPythonに対応してインストールされているバージョンの pip であることが保証されます。システム既定の pip であるか virtual environment にインストールされた pip を適切に使い分けることができます。

2.2. 注意を払い、確認すべき重要な事項:

2.2.1. 除算

Python 3 では、 5 / 2 == 2.5 となります。これは Python2 での結果 2 とは異なっています。 全ての int との間の除算の結果のデータ型は、 float になります。 この変更は、2002年にリリースされたPython 2.2 から計画されました。 それから、ユーザーは from __future__ import division を, /// を使い分けるプログラムあるいは``-Q`` をつけて実行されるプログラムに、追加することを推奨されていました。 もし、あなたがこの対応をまだ実行されていないなら、以下の二つのことを試して見る必要があるでしょう。

  1. from __future__ import division をプログラムファイルの冒頭に追加する。

  2. プログラムの除算を実行している場所で、必要とされる結果が整数の場合には // を、float``の結果が必要な場所では ``/ を使うようにソースコードを変更する。

自動的に /// に単純置換されないには理由があります。 もしオブジェクトに __turudive は定義されいるが、 __floordiv__ は定義されて いない場合、この単純な置換を行なったプログラムは動作しなくなってしまいます。 つまり、ユーザが定義したクラスが、 / を何らかの操作を示すために使っているが、 // は別の意味を持たせているあるいは、それが未定義である場合です。

2.2.2. テキスト対バイナリデータ

Python 2 では str 型をテキストとバイナリデータのどちらにも使うことが出来ていました。不幸なことにこれは、2 つの異なる概念を重ね合わせていて、両方の種類のデータに対して、時々動作して時々はそうではない、といった傷つきやすいコードに繋がりやすいものでした。人々が特定の一つの型の代わりに str を受け付ける何かが、それが許容するのはテキストなのかバイナリデータなのかを名言しないときの、悩ましい API を生み出してしまう要因でもありました。これはとりわけマルチリンガルをサポートするための状況を、テキストデータをサポートしていると主張しているのに明示的に unicode をサポートすることに注意を払わない API、という形で複雑にしていました。

Python3ではテキスト型とバイナリ型は全く別の型として定義されており、単純にこれらの型を 混ぜて使うことはできません。テキストデータ方のみ、あるいは、バイナリデータだけを使うプログラム ではこの分離は問題を起こしません。しかし、両方の型のデータを取り扱うプログラムでは、 この分離は、あなたがテキストデータをバイナリデータと比較して使う場合には適切な対応を取る必要 があることを示しています。これが完全に自動的な変換ができない理由です。

どのAPIがテキストデータを処理し、バイナリデータを処理するAPIはどれであるのかを決定しましょう。 (両方のデータを受け付けるAPIを使わない設計が 強く 推奨されています。これはこの動作を維持する ことの難しさによるものです。先に述べたようにこれはうまくやるには難しいことです。)

Python2では、このことは テキスト型を取り扱うAPIは unicode を、バイナリ型を 処理するAPIでは Python3 由来の bytes を取り扱える必要があることを意味します。 Python2 では bytesstr のサブセットで、bytes 型 の別名として動作します。 通常、最大の問題は、どのメソッドがPython2 および Python3で同時に利用可能なメソッドがどの データ型に存在するのかを理解することです。 テキストデータに対しては、Python2では str, Python3 では unicode が使われます。バイナリデータに対しては、Python2 では str あるいは bytes が使われますが、Python3では bytes が使われます。 Python 2 and bytes in Python 3).

以下のテーブルは、Python2 および Python3で共通な ユニーク なメソッドを示しています。 つまり、 decode() メソッドは Python2でもPython3でも等価なバイナリデータ に対して使うことができます。しかしこのメソッドをテキストデータに対しては、Python2とPython3 に対して一貫性のあるやり方では使うことができません。Python3の str にはこの メソッドが存在しないからです。 Python3.5以降では、 __mod__() メソッドが bytes 型に追加されたことにご注意ください。

Text data

Binary data

decode

encode

format

isdecimal

isnumeric

型の区別を取り扱いに容易とすることは、テキストデータとバイナリデータのencodingとdecoding による変換をプログラムの境界で行うことです。つまり、テキストデータをバイナリ型で受け取った 場合には、直ちにデコードします。また、もしテキストデータをバイナリで送信する必要がある場合には、 可能な限り最後までエンコードを引き伸ばします。この手法により、あなたのプログラムは内部ではテキスト データだけを取り扱い、今どちらのデータを取り扱っているのかを気にする必要がなくなります。

次の課題は、プログラム中の文字リテラルがテキストであるのかバイナリデータであるのかを明確に把握 していることです。 バイナリデータを表す文字リテラルには, b プリフィックスをつけること を忘れてはいけません。テキストには u プリフィックスをつけます。 __future__ モジュールを使うことで、全ての文字リテラルがユニコード文字列(u)であることを強制することもできます、 しかしこれは全ての文字列に b または u をつける方法ほどの有効性がありません。

ファイルを開く時にも注意を怠ってはいけません。 ファイルを開くときにわざわざ b モード で開く(つまり、rb をつけてバイナリファイルを開くこと)ことは常にはしていないかもしれません。 Python3ではバイナリファイルとテキストファイルは明確に区別されていて、相互に互換性はありません。 (詳細は io モジュールをご覧ください。) 従って、ファイルを開く際には、テキストモードで 使われるのか、バイナリモードで使われるのかの決定を 必ず 決定しなければなりません。 また、ビルトインの open() ではなく、 io モジュールの io.open() を使います。 なぜなら、io.open() はPython2とPython3で同じように動作するからです。 古い慣習の codecs.open() を使う必要はもうありません。これはPython2.5との互換性を 保つために必要であったことだからです。

strbytes の生成子はPython2とPython3では、同じ引数に対して 異なった意味を持っています。 Python2で整数に引数を bytes に渡すと、整数を表現する 文字列が作られます (bytes(3) == '3')。しかしPython3では, 指定された整数の長さのnullバイト データが返されます(bytes(3) == b'\x00\x00\x00')。 同様の配慮が、バイトオブジェクトを str() に渡された時に必要です。Python2では単にそのバイトオブジェクトが返されるだけですが (str(b'3') == b'3'), Python3ではバイトデータの文字列表現が返されます(str(b'3') == "b'3'").

最後に、バイナリデータのインデックスによる取り扱いに関する注意です( スライスによる取り扱いでは、特別な注意は必要ありません。) Python2では、 b'123'[1] == b'2' ですが、 Python 3 では b'123'[1] == 50 となります。 なぜなら、Python3においてはバイナリデータは単純にバイナリ数の集合ですから、整数としての 値が返されます。しかるに、Python2では bytes == str ですから、インデックスで指定 された際には、一つの要素からなるバイトデータを返します。 six プロジェクトには、 six.indexbytes() という関数があり、Python3のように指定された場所のバイナリデータに対応する整数を 値として返します (six.indexbytes(b'123', 1))。

まとめておくと:

  1. あなたが定義するAPIのどれがテキストデータを引数にとり、どれがバイナリデータを引数とするのかを決定する

  2. テキストデータの処理を行う際には、unicode データも同じように処理されることを確実にしておくこと。 Python2プログラムのバイナリデータを取り扱う部分では、 bytes も同様に取り扱えること(上記のテーブルを参照のこと)

  3. 全てのバイナリ リテラル文字列は、 b で始め、テキストデータは u で始める。

  4. バイナリからテキストへの変換(decode)は即時に行われること。 逆にテキストからバイナリに 変換される場合には、できるだけ後回しにしておく。

  5. ファイルを開く際は、io.open1() を使うこと。また、必要な場合には、 b モードを 適切につける

  6. バイナリデータのインデックスによる利用には最大の注意を払う。