Python2コードのPython3への移植
- 著者
- ブレットキャノン
概要
Python 2がまだ活発に使用されている間、Python 3はPythonの未来であるため、Pythonの両方のメジャーリリースでプロジェクトを利用できるようにしておくことをお勧めします。 このガイドは、Python2とPython3の両方を同時にサポートする最善の方法を理解するのに役立つことを目的としています。
純粋なPythonコードの代わりに拡張モジュールを移植する場合は、拡張モジュールのPython 3への移植を参照してください。
Python 3が誕生した理由に関するPython開発者のコアな見解を読みたい場合は、NickCoghlanの Python 3 Q&A またはブレットキャノンの Python3が存在する理由 。
移植については、 python-porting メーリングリストに質問をメールで送信できます。
簡単な説明
プロジェクトをシングルソースのPython2 / 3互換にするための基本的な手順は、次のとおりです。
- Python2.7のサポートについてのみ心配してください
- テストカバレッジが良好であることを確認してください( Coverage.py が役立ちます;
pip install coverage
) - Python2と3の違いを学ぶ
- Futurize (または Modernize )を使用して、コードを更新します(例:
pip install future
) - Pylint を使用して、Python 3サポート(
pip install pylint
)でリグレッションが発生しないようにします。 - caniusepython3 を使用して、Python 3の使用をブロックしている依存関係を確認します(
pip install caniusepython3
) - 依存関係がブロックしなくなったら、継続的インテグレーションを使用して、Python 2および3との互換性を維持します( トックス Pythonの複数のバージョンに対するテストに役立ちます。
pip install tox
)。 - オプションの静的型チェックを使用して、型の使用がPython2と3の両方で機能することを確認することを検討してください(例: 使用する mypy Python2とPython3の両方で入力を確認します)。
詳細
Python 2と3を同時にサポートすることの重要なポイントは、開始できることです。 今日 ! 依存関係がPython3をまだサポートしていない場合でも、Python3をサポートするようにコードを今更新できないという意味ではありません。 Python 3をサポートするために必要なほとんどの変更は、Python2コードでも新しいプラクティスを使用してよりクリーンなコードにつながります。
もう1つの重要な点は、Python2コードを最新化してPython3もサポートするようにすることは、大部分が自動化されていることです。 Python 3がテキストデータとバイナリデータを明確にすることでAPIの決定を下さなければならない場合もありますが、現在は低レベルの作業がほとんど行われているため、少なくとも自動変更の恩恵をすぐに受けることができます。
Python 2とPython 3を同時にサポートするためのコードの移植の詳細について読むときは、これらの重要なポイントに留意してください。
Python2.6以前のサポートを終了します
Python2.5をPython3で動作させることはできますが、Python 2.7でのみ動作する必要がある場合は、はるかに簡単です。 Python 2.5を削除するオプションがない場合は、 六プロジェクトは、Python 2.5と3を同時にサポートするのに役立ちます(pip install six
)。 ただし、このHOWTOにリストされているほぼすべてのプロジェクトを利用できるわけではないことを理解してください。
Python 2.5以前をスキップできる場合、コードに必要な変更は、慣用的なPythonコードのように見え続けるはずです。 最悪の場合、場合によってはメソッドの代わりに関数を使用するか、組み込み関数を使用する代わりに関数をインポートする必要がありますが、それ以外の場合、全体的な変換は異質なものではありません。
ただし、Python2.7のみをサポートすることを目指す必要があります。 Python 2.6はもはや自由にサポートされていないため、バグ修正は受けていません。 つまり、あなたは、Python2.6で発生した問題を回避する必要があります。 このHOWTOには、Python 2.6をサポートしないツール( Pylint など)もいくつかありますが、これは時間の経過とともにより一般的になります。 サポートする必要のあるPythonのバージョンのみをサポートする場合は、簡単になります。
setup.pyファイルで適切なバージョンサポートを指定していることを確認してください
setup.py
ファイルには、サポートするPythonのバージョンを指定する適切な trove分類子が含まれている必要があります。 プロジェクトはPython3をまだサポートしていないため、少なくともProgramming Language :: Python :: 2 :: Only
を指定する必要があります。 理想的には、サポートするPythonのメジャー/マイナーバージョンも指定する必要があります。 Programming Language :: Python :: 2.7
。
テストカバレッジが良好
必要な最も古いバージョンのPython2をサポートするコードを入手したら、テストスイートが十分にカバーされていることを確認する必要があります。 経験則として、テストスイートに十分な自信を持って、ツールにコードを書き直させた後に発生する障害は、コードではなくツールの実際のバグであるということです。 目標とする数値が必要な場合は、80 % cの超過を超えてみてください(90 % cの超過を超えるのが難しい場合でも、気分が悪くなることはありません)。 テストカバレッジを測定するツールがまだない場合は、 Coverage.py をお勧めします。
Python2と3の違いを学ぶ
コードを十分にテストしたら、Python3へのコードの移植を開始する準備が整います。 しかし、コードがどのように変更されるのか、そしてコーディング中に何に注意したいのかを完全に理解するには、Python2に関してPython3がどのような変更を加えるのかを学びたいと思うでしょう。 通常、これを行う2つの最良の方法は、Python3の各リリースの「What'sNew」ドキュメントと Porting to Python 3 ブック(オンラインで無料)を読むことです。 Python-Futureプロジェクトからの便利なチートシートもあります。
コードを更新する
Python3とPython2の違いがわかったら、コードを更新します。 コードを自動的に移植するには、 Futurize と Modernize の2つのツールから選択できます。 どのツールを選択するかは、コードをPython3にどれだけ似ているかによって異なります。 Futurize は、Python3のイディオムとプラクティスをPython2に存在させるために最善を尽くします。 Python3からbytes
型をバックポートして、Pythonのメジャーバージョン間でセマンティックパリティを取得できるようにします。 一方、 Modernize はより保守的で、PythonのPython 2/3サブセットを対象としており、互換性の提供を支援するために six に直接依存しています。 Python 3は未来なので、Python 3で導入された、まだ慣れていない新しいプラクティスに適応し始めるために、Futurizeを検討するのが最善かもしれません。
選択したツールに関係なく、Python 3で実行するようにコードが更新され、使用を開始したPython2のバージョンとの互換性が維持されます。 どれだけ保守的になりたいかによっては、最初にテストスイートでツールを実行し、差分を視覚的に調べて、変換が正確であることを確認することをお勧めします。 テストスイートを変換し、すべてのテストが期待どおりに合格することを確認したら、失敗したテストが変換の失敗であることを認識して、アプリケーションコードを変換できます。
残念ながら、ツールはPython 3でコードを機能させるためにすべてを自動化することはできないため、Python 3を完全にサポートするために手動で更新する必要があるものがいくつかあります(これらの手順のどれが必要かはツールによって異なります)。 使用することを選択したツールのドキュメントを読んで、デフォルトで何が修正されるか、オプションで何が修正されるか、何が修正されるか、自分で修正する必要があるかを確認します(例: 組み込みのopen()
機能でio.open()
を使用することは、Modernizeではデフォルトでオフになっています。 ただし、幸いなことに、注意すべき点は2つしかありません。これらは、注意しないとデバッグが難しい大きな問題と見なすことができます。
分割
Python 3では、2
ではなく5 / 2 == 2.5
。 int
値の間のすべての除算は、float
になります。 この変更は、2002年にリリースされたPython2.2以降に実際に計画されています。 それ以来、ユーザーは/
および//
演算子を使用するすべてのファイルにfrom __future__ import division
を追加するか、-Q
でインタープリターを実行することが推奨されています。国旗。 これを行っていない場合は、コードを調べて2つのことを行う必要があります。
from __future__ import division
をファイルに追加します- 必要に応じて除算演算子を更新して、
//
を使用して床除算を使用するか、/
を引き続き使用して浮動小数点を期待します。
/
が単に//
に自動的に変換されない理由は、オブジェクトが__truediv__
メソッドを定義し、__floordiv__
を定義しない場合、コードは次のように変換されるためです。失敗する(例: /
を使用して一部の操作を示しているが、//
を使用していない、またはまったく使用していないユーザー定義クラス)。
テキストとバイナリデータ
Python 2では、テキストデータとバイナリデータの両方にstr
タイプを使用できます。 残念ながら、この2つの異なる概念の合流により、コードが脆弱になり、どちらの種類のデータでも機能する場合と機能しない場合があります。 また、str
を受け入れるものが、特定のタイプではなくテキストまたはバイナリデータのいずれかを受け入れることを明示的に述べていない場合、APIが混乱する可能性があります。 これは、APIがテキストデータのサポートを主張したときにunicode
を明示的にサポートすることを気にしないため、特に複数の言語をサポートする人にとっては状況を複雑にしました。
テキストとバイナリデータの区別をより明確かつ明確にするために、Python 3は、インターネットの時代に作成されたほとんどの言語が行ってきたことを実行し、テキストとバイナリデータを盲目的に混合できない別個のタイプにしました(Pythonは、インターネット)。 テキストのみまたはバイナリデータのみを処理するコードの場合、この分離は問題になりません。 ただし、両方を処理する必要のあるコードの場合、バイナリデータと比較してテキストを使用する場合は注意が必要になる可能性があるため、これを完全に自動化することはできません。
まず、テキストを使用するAPIとバイナリを使用するAPIを決定する必要があります(前述のように、コードの動作を維持するのが難しいため、両方を使用できるAPIを設計しないことを強くお勧めします。うまくやるのは難しいです)。 Python 2では、これは、テキストを受け取るAPIがunicode
で機能し、バイナリデータで機能するAPIがPython 3(str
であり、Python2ではbytes
タイプのエイリアスとして機能します。 通常、最大の問題は、Python 2と3のどのタイプにどのメソッドが同時に存在するかを認識することです(テキストの場合)unicode
Python2およびstr
Python 3では、バイナリの場合はstr
/bytes
Python2およびbytes
Pythonで3)。 次の表に、 個性的 Python 2および3全体の各データ型のメソッド(例:decode()
メソッドは、Python 2または3の同等のバイナリデータ型で使用できますが、Python2と3の間で一貫してテキストデータ型で使用することはできません。str
Python 3にはメソッドがありません)。 Python 3.5以降、__mod__
メソッドがバイト型に追加されたことに注意してください。
テキストデータ | バイナリデータ |
デコード | |
エンコード | |
フォーマット | |
isdecimal | |
isnumeric |
区別を扱いやすくするために、コードの端にあるバイナリデータとテキストをエンコードおよびデコードすることで実現できます。 つまり、バイナリデータでテキストを受信したら、すぐにデコードする必要があります。 また、コードでテキストをバイナリデータとして送信する必要がある場合は、できるだけ遅くエンコードしてください。 これにより、コードは内部でテキストのみを処理できるため、処理しているデータの種類を追跡する必要がなくなります。
次の問題は、コード内の文字列リテラルがテキストデータを表すのかバイナリデータを表すのかを確認することです。 バイナリデータを表すリテラルには、b
プレフィックスを追加する必要があります。 テキストの場合、テキストリテラルにu
プレフィックスを追加する必要があります。 ( __ future __ インポートがあり、指定されていないすべてのリテラルをUnicodeに強制しますが、使用法では、すべてにb
またはu
プレフィックスを追加するほど効果的ではないことが示されていますリテラルを明示的に)
この二分法の一部として、ファイルを開くことにも注意する必要があります。 Windowsで作業していない限り、バイナリファイルを開くときにb
モードを追加する必要がない可能性があります(たとえば、バイナリ読み取り用のrb
)。 Python 3では、バイナリファイルとテキストファイルは明確に区別され、相互に互換性がありません。 詳細については、 io モジュールを参照してください。 したがって、ファイルをバイナリアクセス(バイナリデータの読み取りおよび/または書き込みを許可)またはテキストアクセス(テキストデータの読み取りおよび/または書き込みを許可)のどちらに使用するかを必ず決定する必要があります。 。 io モジュールはPython2から3組み込みの open()関数はそうではありません(Python 3では実際には io.open()です)。 codecs.open()を使用するという時代遅れの慣習を気にしないでください。これは、Python2.5との互換性を維持するためにのみ必要です。
両方のコンストラクターstr
とbytes
Python2と3の間で同じ引数に対して異なるセマンティクスがあります。 Python2でbytes
に整数を渡すと、整数の文字列表現bytes(3) == '3'
が得られます。 ただし、Python 3では、bytes
への整数引数は、指定された整数である限り、nullバイトbytes(3) == b'\x00\x00\x00'
で満たされたbytesオブジェクトを提供します。 バイトオブジェクトをstr
に渡す場合も、同様の心配が必要です。 Python 2では、bytesオブジェクトstr(b'3') == b'3'
を取得するだけです。 ただし、Python 3では、bytesオブジェクトの文字列表現str(b'3') == "b'3'"
を取得します。
最後に、バイナリデータのインデックス作成には注意深い処理が必要です(スライスには特別な処理は必要ありません。 Python 2では、b'123'[1] == b'2'
、Python 3ではb'123'[1] == 50
。 バイナリデータは単に2進数のコレクションであるため、Python3はインデックスを作成したバイトの整数値を返します。 ただし、Python 2ではbytes == str
であるため、インデックス付けは1項目のバイトスライスを返します。 six プロジェクトには、Python 3:six.indexbytes(b'123', 1)
のように整数を返すsix.indexbytes()
という名前の関数があります。
要約する:
- どのAPIがテキストを取得し、どのAPIがバイナリデータを取得するかを決定します
- Python 2では、テキストで機能するコードが
unicode
でも機能し、バイナリデータのコードがbytes
で機能することを確認してください(各タイプで使用できないメソッドについては、上の表を参照してください)。 - すべてのバイナリリテラルに
b
プレフィックスを付け、テキストリテラルにu
プレフィックスを付けます - バイナリデータをできるだけ早くテキストにデコードし、テキストをできるだけ遅くバイナリデータとしてエンコードします
- io.open()を使用してファイルを開き、必要に応じて
b
モードを指定してください - バイナリデータにインデックスを付けるときは注意してください
バージョン検出の代わりに機能検出を使用する
必然的に、実行しているPythonのバージョンに基づいて何をするかを選択する必要のあるコードがあります。 これを行う最良の方法は、実行しているPythonのバージョンが必要なものをサポートしているかどうかを機能検出することです。 何らかの理由で機能しない場合は、バージョンチェックをPython3ではなくPython2に対して行う必要があります。 これを説明するために、例を見てみましょう。
Python3.3以降のPythonの標準ライブラリで利用可能でPyPIの importlib2 からPython2で利用可能な importlib の機能にアクセスする必要があるとしましょう。 アクセスするコードを書きたくなるかもしれません。 importlib.abc
モジュールは、次のようにします。
import sys
if sys.version_info[0] == 3:
from importlib import abc
else:
from importlib2 import abc
このコードの問題は、Python 4がリリースされたときに何が起こるかということです。 Python3ではなくPython2を例外的なケースとして扱い、将来のPythonバージョンはPython2よりもPython3との互換性が高いと想定する方がよいでしょう。
import sys
if sys.version_info[0] > 2:
from importlib import abc
else:
from importlib2 import abc
ただし、最善の解決策は、バージョン検出をまったく行わず、代わりに機能検出に依存することです。 これにより、バージョン検出が間違ってしまうという潜在的な問題が回避され、将来の互換性を維持できます。
try:
from importlib import abc
except ImportError:
from importlib2 import abc
互換性の低下を防ぐ
コードをPython3と互換性があるように完全に翻訳したら、コードがリグレッションを起こさないことを確認し、Python3での動作を停止する必要があります。 これは、現時点でPython3で実際に実行することを妨げている依存関係がある場合に特に当てはまります。
互換性を維持するために、作成する新しいモジュールには、少なくとも次のコードブロックが上部にある必要があります。
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-3
フラグを指定してPython2を実行し、実行中にコードがトリガーするさまざまな互換性の問題について警告することもできます。 -Werror
で警告をエラーに変えた場合、誤って警告を見逃さないようにすることができます。
Pylint プロジェクトとその--py3k
フラグを使用して、コードがPython 3の互換性から逸脱し始めたときに警告を受け取るように、コードをリントすることもできます。 これにより、互換性の低下をキャッチするために、コードに対して Modernize または Futurize を定期的に実行する必要もなくなります。 これには、Pylintの最小PythonバージョンサポートであるPython2.7およびPython3.4以降のみをサポートする必要があります。
どの依存関係が移行をブロックしているかを確認してください
コードをPython3と互換性のあるものにした後、依存関係も移植されているかどうかを気にする必要があります。 caniusepython3 プロジェクトは、直接的または間接的に、Python3のサポートをブロックしているプロジェクトを特定するのに役立つように作成されました。 https://caniusepython3.com には、コマンドラインツールとWebインターフェイスの両方があります。
このプロジェクトは、テストスイートに統合できるコードも提供しているため、Python 3の使用を妨げる依存関係がなくなったときに、テストが失敗する可能性があります。 これにより、依存関係を手動で確認する必要がなくなり、Python3での実行を開始できるときにすぐに通知を受けることができます。
setup.pyファイルを更新して、Python3の互換性を示します
コードがPython3で機能するようになったら、setup.py
の分類子を更新して、Programming Language :: Python :: 3
を含め、Python2のサポートのみを指定しないようにする必要があります。 これにより、コードを使用しているすべての人に、Python 2 および 3をサポートしていることがわかります。 理想的には、現在サポートしているPythonのメジャー/マイナーバージョンごとに分類子を追加することもできます。
継続的インテグレーションを使用して互換性を維持する
Python 3で完全に実行できるようになったら、コードが常にPython2と3の両方で機能することを確認する必要があります。 おそらく、複数のPythonインタープリターでテストを実行するための最良のツールは tox です。 その後、toxを継続的インテグレーションシステムと統合して、Python2または3のサポートを誤って壊すことはありません。
また、Python 3インタープリターで-bb
フラグを使用して、バイトを文字列に、またはバイトをintに比較するときに例外をトリガーすることもできます(後者はPython 3.5以降で使用可能です)。 デフォルトでは、型の異なる比較は単にFalse
を返しますが、テキスト/バイナリデータの処理またはバイトのインデックス作成の分離を間違えた場合、間違いを簡単に見つけることはできません。 このフラグは、これらの種類の比較が発生したときに例外を発生させ、間違いを追跡しやすくします。
そして、それはほとんどそれです! この時点で、コードベースはPython2と3の両方と同時に互換性があります。 また、テストは、開発中にテストを通常実行するバージョンに関係なく、Python2または3の互換性を誤って壊さないように設定されます。
オプションの静的型チェックの使用を検討してください
コードを移植するのに役立つもう1つの方法は、コードで mypy や pytype などの静的型チェッカーを使用することです。 これらのツールを使用して、Python 2で実行されているかのようにコードを分析できます。次に、コードがPython3で実行されているかのようにツールをもう一度実行できます。 このように静的型チェッカーを2回実行することで、たとえば Pythonのあるバージョンでバイナリデータ型を別のバージョンと比較して誤用している。 オプションの型ヒントをコードに追加すると、APIがテキストデータとバイナリデータのどちらを使用するかを明示的に指定できるため、両方のバージョンのPythonですべてが期待どおりに機能することを確認できます。