rcmdnk's blog
Last update

エキスパートPythonプログラミング

Brew-file はpython製ですが、基本自分の環境が2.7なので 3.Xの方では全然テストしてませんでしたが、 3.Xで動かない、といったissueが来たので 全面的に見なおして3.Xでも動くようにしてみました。

2.7でも引き続き使える様にPython2.XとPython3.Xの共存コードになっています。

Python2.XとPython3.Xの共存

Pythonは2.Xと3.Xで非互換な変更があり、大体のスクリプトはどちらかでしか動きません。 非互換部分が多すぎて、別途書き直したほうが良い位の事もあったりしますが、 今回、Brew-fileではは取り敢えず現状のもので両方で使える様にしてみました。

以下、今回行った変更点等。

Python2.Xで標準出力したい時などは

1
print 'aaa'

みたいにprint文を使いますが、 これを3.Xで使うと、

File "<stdin>", line 1
  print 'aaa'
            ^
SyntaxError: Missing parentheses in call to 'print'

な感じで怒られます。3.Xではprint

1
print('aaa')

の様に関数の形になっています。 これによって大概のスクリプトは互換性を失います。

これを共存させるためには、Python2.X用に

1
from __future__ import print_function

を導入します。このimportを行うと、2.Xでも printは関数の形になります。(先の文の形は使えなくなる。)

他にも__future__にはいくつか3.Xの動きを2.Xに取り入れるためのモジュールがあります。

28.11. future — future 文の定義 — Python 2.7.x ドキュメント

中でwith_statementなんかは2.6から導入されたwith構文を使える様にするためのものですが、 これは3.Xの機能、というわけではなく、2.5以前の物に対してwithを使える様にするためのモジュールです。

xrangeは無い

2.Xにあったxrangeは3.Xにはありません。

2.Xではrangeはその範囲のリストを返すもの、 xrangexrangeオブジェクトと呼ばれるオブジェクトを返して これを使うことで値を遅延評価してメモリを一気に使うことを避けることが出来ます。 なので非常に大きな範囲を使う時にはxrangeが大分高速だったりします。

3.Xではrangeが単なるリストでは無く、rangeオブジェクトを返す様になり、 xrangeと同じような動作をするのでこれを使えば良し、と言うことに。

今回のコードではそれ程大きな数を扱うことはないので xrangeを使ってる部分は全てrangeに置き換えて両方で使える様にしました。

速度が気になる様なレベルで使っている場合にはちょっと考えないといけません。

exceptの待受

exceptでエラーの出力を取るために、2.Xでは

1
2
except OSError, e
    print str(e)

的に、, eとして変数に入れていましたが、3.Xでは,が別の意味を持つため使えず、

1
2
except OSError as e
    print str(e)

のようにasを使います。 この方法は2.Xでも使えるのでこの様に変更。

map/filter

map/filterが返すものは2.Xではリストでしたが、 3.Xではそれぞれ、map、filterオブジェクトになります。

なのでこれを他のlistと直接足したりしようとすると

>>> [1, 2, 3] + map(lambda x: x + x, [1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "map") to list

な感じのエラーが置きます。

これは単にlistに変換してあげることで解決できるので、

>>> [1, 2, 3] + list(map(lambda x: x + x, [1, 2, 3]))

とキャストしてあげればOK。(基本、2.Xのコードなら、mapfilterの所を全てlistでキャストしてしまっても問題ないはず。)

2.Xでもこれをしても問題ないので一通り、mapしたものをlistと結合しようとする場では キャストしておきます。

subprocessの出力のdecode

2.Xではsubprocessからの出力をそのまま文字列として使っていましたが、 3.Xではbytes型で帰ってくるのでこれをデコードしてやる必要があります。 (というか2.Xではbytesstrと同義だったのが別物になった。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
is_python3 = -1


def my_decode(word):
    global is_python3
    if is_python3 == -1:
        try:
            unicode
            is_python3 = 0
        except:
            is_python3 = 1
    if is_python3 == 0:
        return word
    else:
        return word.decode()


p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT)

lines = []
for line in p.stdout:
    l = my_decode(line).rstrip()
    lines.append(l)

こんな感じでp.stdoutを見るとき、 Python3の時にはそれぞれ.decode()してstrに変更します。

また、Python2の時には

1
2
for line in iter(p.stdout.readline, ''):
    ...

みたいにiterを使ったりしていましたが、この方法だと3.Xでは b''が延々と続いて終わりません。

素直に上のようにp.stdoutを取る方法で両方で使えます。

(かなり適当ですが取り敢えず使える方法と使えない方法ということで。)

StringIO

StringIOの置き場所が変わったので以下のように 2.X用の物を試してダメなら3.X用の物を読みこむように。

1
2
3
4
5
6
try:
    # For 2.X
    from cStringIO import StringIO
except ImportError:
    # For 3.X
    from io import StringIO

追記: 2016/04/15

ここで3.X用に from io import StringIOから先にtryするように書いてましたが、 実は2.Xにもio.StringIOは存在していて、その場合io.StringIO.write()の 引数がunicodeになってstrを普通に入れようとするとエラーが出てしまいます。

2.Xを先に入れると2.Xで使っていたコードがそのまま使えます。

追記ここまで

Pythonのバージョンを直接チェックして

1
2
3
4
5
import platform
if platform.python_version_tuple()[0] == '2':
    from cStringIO import StringIO
else:
    from io import StringIO

みたいにすることも出来ますが、 この手の互換性を入れようとしてるコードは上の様に直接 例外処理でやってるものが見た感じほとんどです。 これだと3.Xとかでなくても、変更があった時点で切り替わるので便利なので。

urllib2

Python2.Xでurllib2のモジュールとしてあったurlopen等は 3.Xではurllib.requestにあります。

HTTPErrorurllib.errorに。

従ってこれらはモジュールを直接インポートするようにして、

1
2
3
4
5
try:
    from urllib.request import urlopen
    from urllib.error import HTTPError
except:
    from urllib2 import urlopen, HTTPError

な感じにしておいて使います。

参考:

Cheat Sheet: Writing Python 2-3 compatible code — Python-Future documentation

2to3を使ってコードをPython 3に移植する - Dive Into Python 3 日本語版

Python 2.7.x と 3.x の決定的な違いを例とともに プログラミング POSTD

Sponsored Links
Sponsored Links

« Travis CIで複数の暗号化ファイルを使う方法 HomebrewのFormulaとCaskで重複してるアプリについて »

}