Windows で Python2.7 から Python3.6 に移行した

前提(Python環境)

  • Python2.7 をインストールしている
    • 環境変数 PATH を通している
    • 拡張子 .py への関連付けも設定している
  • Windows7 x86Windows7 x64、Windows10 x64 の三環境で使っている

前提(Pythonコード)

  • Python 2.7 で書いてる
  • 1KL に満たない小さなコードで個人的に省力化している
    • 製品や Social な OSS の開発はしてません。ただの個人改善&趣味レベルの話です
  • 常用しているものもあり動かなくなると(私がストレスで)死ぬ
  • 主にやってること
    • 標準出力
    • テキストファイルの読み書き
    • システムコマンドのラッパーコマンド作成
    • win32api でウィンドウやらクリップボードやキーボードやマウスあたりを制御
    • REST API の利用
  • 使用ライブラリ
    • requests
    • pywin32
    • py2exe
  • 起動方法
    • python hoge.py
    • hoge.py ← 関連付けが働いてる
    • hoge ← 関連付けと PATHEXT が働いてる

ゴール

  • 実行環境
    • Python2.7 の実行環境は残したままにする
    • Python3.6 の実行環境を新たに追加し、PATH と関連付けをこっちにシフトさせる
  • コード
    • Python3.6 で動くようにする

作業の流れ

サンドボックスPython環境置き換え、の 2 ステップで実施した。

(1) サンドボックス

まずはサンドボックス的に(Python2.7ベースの現環境を汚さないように) 最低限常用しているコードだけは全部修正完了させる。常用スクリは呼吸のごとく染み付いているので、これらが使えなくなるとストレスで死ぬ。

手順:

  • Python 3.6 をインストールする
    • ただし PATH や関連付けはいじらない
  • set path=D:\bin1\Python36;D:\bin1\Python36\Scripts;%path%
    • デフォだと Python 2.7 が使われてしまうので、こんな感じで一時的に 3.6 が使われるようにする
  • pip install requests
  • pip install pypiwin32
    • pywin32 の Build 220 に相当(16/01/11のブツ)
  • 作業ディレクトリを用意し、ここに 3.6 移行コードを配置し、修正&動作確認していく

一通り修正できたら、いよいよ Windows のデフォを 3.6 に移す。

(2) Python 環境を 2.7 から 3.6 に置き換える

(1)環境変数 PATH を変更する

D:\bin1\python27
D:\bin1\python27\Scripts

↓

D:\bin1\python36
D:\bin1\python36\Scripts

Rapid Environment Editor を使うと楽。あと変更後の内容を反映させるために、立ち上げてるアプリをいったん全部終了して再起動し直す。プログラムランチャは AutoHotkey なども忘れないように。

(2).py ファイルへの関連付けを変更する

HKEY_CLASSES_ROOT\Python.File\shell\open\command
"D:\bin1\python27\Python.exe" "%L" %*

↓

"D:\bin1\python36\Python.exe" "%L" %*

見ての通りレジストリをいじる。PATH と同様、アプリを立ち上げ直すのを忘れずに(そうしないと反映されない)。あと、win7 x86 の場合はこのやり方ではなく「.py ファイルを右クリックして プログラムから開く より直接関連付けさせる」やり方でないとダメかもしれない(自分はそうだった)。

以上。

以降は細かい TIPS などを。

(参考) Python 3 のバージョンはなぜ 3.6 ?

Ans: 移行を検討し始めた去年時点での最新が 3.6.x だったから&最新といってもベータではなく正式リリースになっていたから。

(参考) サンドボックスでどこまで移行するべきか

たとえばコードが全部で 200 ファイルあったとして全部を移行させるの?100KL あったとして全部書き直すの?という話。

Ans: No.

もっというと まずは常用しているコード(1日1回は触るもの)のみ移行する でよいと思う。

私の場合、自製のタスク管理ツール、Git コマンドラッパー、エイリアス管理ツールなど色んな Python スクリを常用しているので、これらは書き直す必要があった。サンドボックスで動作確認まで済ませた。

なお、コード自体は常用分以外にも山ほどあったが、手を付けていない。必要になったらその時に python3 に書き直せばいいと思っている。

(参考) 変換ツールは使わないの?

Ans: かえって手間かかりそうだったので使わなかった。

Python の勉強会にて変換ツールについて訊いたところ、 2to3 が定番とのこと。しかし全てを完全に変換してくれる銀の弾丸ではなく、結局手直しも必要になるらしい。

私の場合、時間はたっぷりある&過去のコードを読み返して断捨離したい、ということで手作業で良いかと判断した。

(参考) 2.7 のコードを 3.6 に変更する

Python 2 と 3 には互換性が無いので、結構大掛かりな変更が必要になる。

ここからは具体的にどこをどのように変更したのかを簡単にまとめたい。上がpython2のコードで、下がpython3のコードである。

print

print xxx
print yyy,
print(xxx)
print(yyy, end='')

結果を stdout するスクリを多数作ってるせいで修正量が多い。正規表現に頼ろうとしたけど、print XXXX この XXXX 部分に色んなパターンがあって全部を網羅する表現が無理ゲーだったので断念。頑張って手で変更。

format

'%s %02d' % (str1, int1)

'{:} {:02d}'.format(str1, int1)

raw_input

s = raw_input(...)

s = input(...)

python2 には raw_inputinput があったが、python3 では input のみ。

file

def read_from_file(filepath):
    f = file(filepath, 'r')
    ret = f.read()
    f.close()
    return ret

def write_to_file(filepath, content):
    f = file(filepath, 'w')
    f.write(content)
    f.close()
def read_from_file(filepath):
    ret = None
    with open(filepath, encoding='utf8', mode='r') as f:
        ret = f.read()
    return ret

def write_to_file(filepath, s):
    """ @param s A string you want to write. """
    with open(filepath, encoding='utf8', mode='w') as f:
        f.write(s)

file 関数は存在しないので open を使う。

また open は encoding 引数が必要、かつ python2 でいうところの unicode string を返してくる(ので bytestring を扱うこと前提のコードだとエラー量産)。

ConfigParser

まずはモジュール名が camelcase に。

import ConfigParser

import configparser

それからファイルオブジェクトも encoding が必要。

cp = configparser.RawConfigParser()
cp.read(DATAFILE_PATH)
cp = configparser.RawConfigParser()
cp.read(DATAFILE_PATH, encoding='utf8')

string

  • u'foobar''foobar'
  • 'foobar'b'foobar'
  • encode()decode() は基本的に使わなくて済むはず
  • ...

文字列。一番ややこしい部分。

python2では以下のようになっていたが、

python3では以下のように変更された。

  • string(python2でいうunicode stringのこと)
  • bytestring(python2でいうstringのこと。sjisとかutf8とか)

python2 では string と unicode string の変換を全部自分で行う必要があった。たとえば読み込むファイルの文字コードsjis で、書き込みたいファイルの文字コードが utf8 で、読み込んだ内容を使って書き込みたい場合、そのまま string で扱うと文字コード違って扱えないから、sjisunicode string に decode してからあれこれいじって、書き込む時にそれを utf8 に encode して……みたいなことが必要だった。

python3 では unicode string だけ使えばいいように上手いことラップされた。

つまり文字列に絡む python 2 から 3 への移行作業は、2 で自前で行っていた変換を廃止する(廃止して 3 のラップ機構にまかせてしまう)のがメイン。ただし表記もがらりと変更されてるので print 並に面倒くさい。

暗黙的な相対インポートの廃止

dir1/file1.py と dir1/file2.py があったとして、file1.py から file2.py を import する場合。

from file2 import File2_Class1

from dir1.file2 import File2_Class1

このように書く必要がある。python2 では上の書き方でも「dir1 の file2 のことだよね!」と補われていたが、Python3 では補ってくれない。

詳しい仕組みは importの躓きどころ - Qiita がわかりやすい。

(参考) 2.7 のコードを 3.6 に変更する(外部ライブラリやツール)

続いて外部ライブラリやその他ツール周り。

py2exe

Python スクリプトの実行ファイル化。

今までは py2exe を使っていたのだが、こいつは Python 3.6 に対応してない。そもそも 最新版 が 2008-11-26 の バージョン 0.6.9 と古い。

cx_Freeze にした。最新版 は 2017-12-16 の バージョン 5.1.1 で、あまり古くない。 インストールも pip install cx_Freeze 一発で済む。

PyScripter

PyScripterPython 用の IDE

今まではバージョン 2.5 を使っていたが、これは Python3.3 までしかサポートしてないので、バージョン 3.0 を使う。(3.1 はベータ版でまともに動作しないので注意)

おわりに

Pyrhon2 は 2020 年までしかサポートされないし、2018/01 現時点で GitHub でもほとんどオワコンでコードを公開しても見向きもされないし、でいいかげん移行したかった。

とりあえず必要最小限の移行は終わったので良しとする。思っていたほど手間取らなかったな。慣れたら、100-200Lくらいのコードなら数分で変更できる。もっと早めに着手すればよかった。

さあ、これからは Python 3 の世界でガシガシ書いてくぞ。