標準ライブラリに学ぶ Python 3.0移行のポイント

2009/03/11 19:30から行われたPython Code Reading 08 お題
標準ライブラリに学ぶ Python 3.0 移行のポイント
by ふるかわとおる


内容的には、Python 3 に移行した際に、発生するであろうトラブルや、どのように移行すべきか、問題を直すべきか、といったものです。
個人的にとてもとてもニーズのある話題で、ブラボーブラボーな感じです。
Python Code Readingに参加するような層のメンバーには、必要になることも多いでしょう。

Python 3.0 のベネフィット

より分かりやすくPythonのコードを書けるようになった
あるべき形に、3.0へのメジャーバージョンアップにともない移行した

Python 3.0 のデメリット

後方互換性がない
結果、動かなくなるコードが多数でてしまう。

動かなくなったコードを修正するには

print文

2.Xの場合

print ...
print ...,
print ...
print >>stream, ...

3.0 では、printが関数になりました。
ちょっとしたコードを書くときは、今までより、面倒なことになりそうです。

print(...)
# print関数で、出力後、改行したくない時はendを指定する。
print(..., end='')


2.Xの次のコードは、

print "spam",

3.0ではこう書きます。

print ("spam", end='')
文字列
  • unicodeがベースに
  • bytes型が追加された
  • u"..."が廃止。
  • b"..."が追加。


base64.pyモジュールの

def encodestring(s):

に、従来どおりstringを渡すと、TypeErrorを投げてしまう、という報告がありました。
同関数は、3.0でbytes型を受けるよう変更された。


このモジュールと似たような修正が入るモジュールは多いでしょう。
3.0に移行すると、あちこちでTypeErrorが投げられることになりそう。

ファイル

2.Xの場合

return open(filename).read(),

3.0では、デコードした文字列が返るように。エンコードはファイルを開く時に指定。本来あるべき形でしょう。
今までのコードを置き換える場合、一番面倒そうなのが、このファイルの部分だと思います。
エンコード指定箇所と、ファイルを開く場所が離れていると、面倒なことになるだろう。

return open(filename, encoding=encoding).read(),


バイナリの書き込みはbytes型を使用する。


3.0では、ファイル読み込み時点でエンコーディングを指定します。
文字化けなどの理由で、bytes型でファイルを開かねばならない時もあるかもしれない。

例外のキャッチ

2.Xでの例外のキャッチ

try:
    pwd = os.getcwd()
# catchしたos.errorがmsgに入る
except os.error, msg:
    print "os.error"

2.X。複数の例外をキャッチする場合

try:
    pwd = os.getcwd()
except (ValueError, IndexError), var:
    print "os.error"


一方、3.0の例外の指定方法。
気のせい程度ですが、こちらの方が分かりやすいですね。

try:
    pwd = os.getcwd()
except os.error as msg:
    print "os.error"
例外のスロー

2.Xの場合

# クラスAttributeError 変数varに入れて投げる
raise AttributeError, var

3.0の場合
こちらの方が、言語仕様知らない人でも、わかりやすい。

raise AttributeError(var)
文字列例外の廃止

文字列例外がなくなった。
置き換える時はどうする? 例外クラスを定義するべきか、他の例外で指定すべきか。
例外は全体に分かりやすくなったように思います。

raise "MyException"
イテレータ

2.Xの場合

iter.next()

3.0の場合

next(iter)


3.0の方がわかりにくいような? そんな大差ある訳じゃないですけど。
3.0に移行するなら、割と修正が発生しそうな内容です。

辞書

keys() items(), values()がリストを返すのではなく、ビューを返すようになった。
ビューは、元のリストを返ると、表示される内容も変わってしまうもの。


2.Xの場合

# リストを返す
environ.keys()

3.0の場合

# ビューを返す
# 見た目は変わらない
environ.keys()

# しかし、データの変更にあわせて、ビューも変更される
view = environ.keys()
environ["xxx"] = ....
print(environ)


この変更はトラブルを多数発生させそう。
防御的コピーを使って、データが変更されないように保護すべき場面もありそうだ。

他、いろいろ

廃止 __comp__() 。代わりに __lt__() を使用すれば良い。
ただし、pythonでは、-1もtrueになるので、単純に置き換えるのはアブない。


廃止 __getitem__() 。代わりに __getslice__()
廃止 file() 。代わりに open()
廃止 <> 。代わりに !=
廃止 __nonzero__ 。代わりに __bool__


たくさんあるが、詳しくは、What's New in Python 3.0に書いてある。

メタクラスの定義方法

メタクラスの定義

class Foo:
    __metaclass__ = c

メタクラスの定義
わかりやすい。

class Fo(metaclass=c)

変換ツール 2to3 コマンド

# diffを出力
2to3 test.py
# ファイルに出力
2to3 -w out.txt test.py
できること
  • モジュールの位置移動に対応している
  • print文の置き換え
  • イテレータの置き換え
できないこと
  • __cmp__メソッドしかない場合、__lt__メソッドを追加してはくれない。
  • sliceの変換に失敗
  • ファイルを開くところ。開く時点でエンコード指定が必要。
  • reduce() は変換しない

3.0に移行すべきか

ほとんどは局所的に修正すれば大丈夫。
文字列はファイルなどの境界での修正で対応。

感想

今回の内容はとても良かった。
事前に配られた資料がかなりのボリュームで、これだけでも読む価値ありました。