古き良きPython 2プログラムをなんとかPython 3で動かす方法

みなさん、こんにちは。

Python 3がリリースされてから久しく、そしてPython 2は2020年1月1日をもって公式サポートを終了しました。セキュリティアップデートもバグ修正も行われないため、新規開発でPython 2を選択することはまずありません。しかし、世の中には未だ数多くのレガシーなPython 2コードが稼働しています。

「古いコードだからそのまま動けばいいや」と思っていませんか?サポート終了したソフトウェアを使い続けることは、セキュリティリスクの増大、新しいハードウェアやOSとの非互換性、最新ライブラリの恩恵を受けられないといった、多くの問題を引き起こします。

しかし、「どうしてもPython 2のコードをPython 3環境で動かしたい」という切実なニーズが存在することも事実です。

今回はそんな時のために、いくつかの実践的な方法をご紹介します。理想はPython 3への完全移行ですが、それが難しい場合の現実的な選択肢として参考にしてください。

 


 

Python 2とPython 3の根本的な違い

 

まず、Python 2のコードがPython 3でそのまま動かない理由を理解することが重要です。両バージョンには、互換性のない大きな変更が多数導入されました。代表的なものとしては以下が挙げられます。

  • print文の関数化
    • Python 2ではprint "Hello"でしたが、Python 3ではprint("Hello")のように関数になりました。
  • 整数除算の変更
    • Python 2では5 / 22(整数)でしたが、Python 3では2.5(浮動小数点数)になります。Python 2で浮動小数点数を得るにはfrom __future__ import divisionが必要でした。
  • 文字列とバイト列の分離
    • Python 2ではstr型がバイト列とUnicode文字列の両方を扱えましたが、Python 3ではstr型がUnicode文字列を、bytes型がバイト列を扱うよう明確に分離されました。これは特にファイルI/Oやネットワーク通信で問題を引き起こしやすい部分です。
  • 例外処理の構文
    • Python 2ではexcept SomeError, e:でしたが、Python 3ではexcept SomeError as e:となりました。
  • xrangeからrangeへの統合
    • Python 2のxrangeはイテレータを返しましたが、Python 3ではrangeがその機能を統合し、xrangeはなくなりました。
  • 辞書(Dictionary)のkeys(), values(), items()
    • Python 2ではリストを返しましたが、Python 3ではビューオブジェクトを返します。

これらの違いがあるため、Python 2のスクリプトをPython 3インタプリタで直接実行しようとすると、構文エラーや実行時エラーが頻発します。

 


 

方法1 – subprocessモジュールを利用してPython 2プログラムを外部プロセスとして実行する(推奨)

 

最も堅牢で一般的なアプローチは、Python 3のプログラムからPython 2のスクリプトを別のプロセスとして起動する方法です。Python 3のsubprocessモジュールを使用します。これにより、Python 2のスクリプトは自身のPython 2インタプリタで実行されるため、互換性の問題を回避することができます。

Step 1: Python 2のプログラムを作成する

Python 2で動作するサンプルスクリプトを作成しましょう。ここでは、簡単なメッセージ表示と数値計算を行う例を示します。

# python2_worker.py (Python 2で保存)

# Python 2のprint文
print "Hello from Python 2 worker!"

# 関数定義 (Python 2構文)
def calculate_sum(a, b):
    # 整数除算の挙動の違いを示す
    print "Python 2: 5 / 2 =", 5 / 2
    return a + b

# コマンドライン引数を受け取って処理する例
if __name__ == "__main__":
    import sys
    if len(sys.argv) > 2:
        try:
            num1 = int(sys.argv[1])
            num2 = int(sys.argv[2])
            result = calculate_sum(num1, num2)
            # Python 2のprint文
            print "Result of calculation:", result
        except ValueError:
            print "Invalid arguments provided."
        except Exception as e:
            print "An error occurred:", e
    else:
        print "No arguments provided for calculation."

Step 2: Python 3のプログラムからPython 2スクリプトを呼び出す

次に、Python 3のスクリプトから上記のPython 2スクリプトをsubprocessモジュールで実行してみます。

# python3_main_app.py (Python 3で保存)

import subprocess
import sys

# 実行するPython 2インタプリタのパス
# 環境によって 'python2' コマンドのパスが異なる場合があります。
# 例: /usr/bin/python2, /usr/local/bin/python2 など
# コマンドが見つからない場合は、フルパスを指定してください。
PYTHON2_EXECUTABLE = 'python2' # または '/usr/bin/python2' など

def run_python2_script(script_path, *args):
    """
    Python 2スクリプトを外部プロセスとして実行し、出力をキャプチャする。
    """
    command = [PYTHON2_EXECUTABLE, script_path] + list(args)
    
    print(f"\n--- Running Python 2 script: {command} ---")
    try:
        # subprocess.run() でコマンドを実行
        # capture_output=True で標準出力と標準エラー出力をキャプチャ
        # text=True で出力を文字列としてデコード (Python 3のデフォルトはバイト列)
        # check=True でエラーが発生した場合に例外を発生させる
        result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8')
        
        print("Standard Output from Python 2 script:")
        print(result.stdout.strip())
        
        if result.stderr:
            print("Standard Error from Python 2 script (if any):")
            print(result.stderr.strip())
            
        return result.stdout.strip()
        
    except FileNotFoundError:
        print(f"Error: Python 2 interpreter '{PYTHON2_EXECUTABLE}' not found. "
              "Please ensure it's installed and in your system's PATH, or provide the full path.")
        sys.exit(1)
    except subprocess.CalledProcessError as e:
        print(f"Error executing Python 2 script: Command '{' '.join(e.cmd)}' returned non-zero exit status {e.returncode}.")
        print("Captured stdout:")
        print(e.stdout.strip())
        print("Captured stderr:")
        print(e.stderr.strip())
        sys.exit(1)
    except Exception as e:
        print(f"An unexpected error occurred: {e}")
        sys.exit(1)

if __name__ == "__main__":
    # 引数なしで実行
    print("--- Test Case 1: Running without arguments ---")
    output_no_args = run_python2_script('python2_worker.py')
    print(f"Python 3 received: '{output_no_args}'")

    # 引数を与えて実行
    print("\n--- Test Case 2: Running with arguments ---")
    arg1 = 15
    arg2 = 25
    output_with_args = run_python2_script('python2_worker.py', str(arg1), str(arg2))
    print(f"Python 3 received: '{output_with_args}'")

    # Python 2の出力から結果をパースする例
    # (実際のユースケースでは、JSONなどを介してデータを渡すのがより堅牢です)
    if "Result of calculation:" in output_with_args:
        try:
            # 例として、出力から結果の数値部分を抽出
            result_line = [line for line in output_with_args.splitlines() if "Result of calculation:" in line][0]
            calculated_value_str = result_line.split("Result of calculation:")[1].strip()
            calculated_value = int(calculated_value_str) # または float()
            print(f"Parsed calculation result: {calculated_value}")
        except Exception as e:
            print(f"Failed to parse result from output: {e}")

この方法のメリット

  • 高い互換性
    • Python 2のスクリプトは完全にPython 2環境で実行されるため、互換性の問題を心配する必要がありません。
  • 分離されたプロセス
    • Python 3のアプリケーションとPython 2のスクリプトは完全に分離されており、一方のクラッシュがもう一方に直接影響を与えにくいです。
  • 制御のしやすさ
    • subprocessモジュールは、コマンドライン引数の渡し方、標準入出力のリダイレクト、実行結果の取得、エラーハンドリングなど、多くの制御オプションを提供します。

注意点

  • Python 2のインストール
    • 実行するシステムにPython 2インタプリタがインストールされ、パスが通っている必要があります。
  • データ連携
    • 複雑なデータをプロセス間でやり取りする必要がある場合、標準入出力だけでは不十分です。JSON形式の文字列をやり取りしたり、一時ファイルを使用したり、より高度なIPC(Inter-Process Communication)メカニズム(ソケット通信など)を検討する必要があります。

 


 

方法2 – 2to3ツールによるPython 3へのコード変換(推奨:長期的な解決策)

 

もしPython 2のコードが比較的管理可能なサイズで、今後も継続して利用する予定があるならば、2to3ツールを使ってPython 3のコードに変換し、Python 3環境に完全に移行することをお勧めします。これは最も根本的な解決策であり、長期的な保守性を確保できます。

2to3はPythonに標準で付属しているスクリプトで、Python 2のコードをPython 3に自動的に変換してくれます。

使用方法

コマンドラインで以下を実行します。

2to3 -w your_python2_script.py
  • -wオプション
    • 元のファイルを変換後の内容で上書きし、元のファイルのバックアップ(例: your_python2_script.py.bak)を作成します。
  • -oオプション
    • 変換後のコードを別のディレクトリに出力したい場合に使用します。
  • -fオプション
    • 特定の変換(fixer)のみを適用したい場合に指定します。
  • -xオプション
    • 特定の変換を除外したい場合に指定します。

# test_py2.py (元のPython 2ファイル)
def greet(name):
    print "Hello, %s!" % name

greet("World")
print 5 / 2

2to3 -w test_py2.pyを実行すると、test_py2.pyは以下のように変換されます。

# test_py2.py (変換後)
def greet(name):
    print("Hello, %s!" % name)

greet("World")
print(5 / 2)

そして、test_py2.py.bakとして元のファイルが保存されます。

2to3のメリット

  • 自動化
    • 多くの一般的なPython 2to3の変更を自動的に処理してくれます。
  • 長期的な解決策
    • Python 3に完全に移行できるため、将来的な互換性の問題を解消できます。
  • パフォーマンス
    • 変換後はネイティブなPython 3コードとして実行されるため、外部プロセス起動のオーバーヘッドがありません。

注意点

  • 完璧ではない
    • 2to3はすべての変換を完璧にこなせるわけではありません。特に、以下のようなケースでは手動での修正が必要です。
      • 文字列エンコーディングの複雑な扱い(バイト列とUnicode文字列の混在)
      • 動的にモジュールをインポートする部分
      • 特定のサードパーティライブラリがPython 2とPython 3でAPIが変更されている場合(例: urllib, httplibなど)
      • dict.has_key()など、Python 3で削除されたメソッドや関数
  • テストの重要性
    • 変換後には必ず徹底的なテストが必要です。元のPython 2プログラムが持っていた挙動が、Python 3で正しく再現されるかを確認してください。

 


 

方法3 – コミュニケーションプロトコルを利用したIPC (Inter-Process Communication)

 

subprocessモジュールでの標準入出力によるデータ交換が限界に達した場合、より高度なIPC手法を検討することになります。

  • 一時ファイル
    • Python 2のプログラムが処理結果をファイルに書き出し、Python 3のプログラムがそのファイルを読み込みます。
  • ソケット通信
    • Python 2とPython 3のプログラム間でTCP/IPソケットを開き、ネットワーク経由でデータをやり取りします。これにより、両者が独立したサービスのように振る舞うことが可能になります。
  • メッセージキュー
    • RabbitMQやKafkaのようなメッセージキューシステムを介して、非同期にデータをやり取りします。

これらの方法は実装が複雑になりますが、より大規模なシステムや、リアルタイム性が求められるケースで有効です。ただし、ここまでするなら、実装やテスト工数などを考えてもpython3で書き直すことをお勧めします。

 


 

結論 – 最適なアプローチの選択

 

Python 2プログラムをPython 3環境で動かすための最適なアプローチは、状況によって異なります。

  1. 一時的な利用や最低限の連携が必要な場合
    • subprocessモジュールを利用して、Python 2スクリプトを外部プロセスとして呼び出す方法が最も手軽で推奨されます。
  2. 長期的な保守性と安定性を求める場合、またはコードが比較的小規模な場合
    • 2to3ツールを使用してPython 3へコードを変換し、完全にPython 3に移行することが強く推奨されます。これは、セキュリティ、パフォーマンス、将来的な互換性の面で最善の選択です。
  3. 複雑なデータ連携やリアルタイム性が必要な場合
    • 一時ファイル、ソケット通信、メッセージキューなどのIPCメカニズムを検討します。ただし、実装が複雑になるためあまりお勧めできません。

いずれの方法を選択するにしても、レガシーなPython 2コードの存在は、システムの健全性にとってリスクとなります。可能な限り、最終的にはPython 3への完全移行を目指すことが、持続可能な開発の鍵となります。

私の場合、小規模なコードの場合は1の方法をとり、それ以外は2の方法をとるか完全に書き直すようにしています。

この記事が、みなさんのレガシーコードとの戦いの一助となれば幸いです。

 

本日も最後までお読みいただきありがとうございました。

それでは、よいPythonライフを!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール