Python3とPython2で両方で動くコードを書こうとしてみた
Python3には、2系で作られたコードを3用に変換する案をパッチを生成する2to3というコマンドラインツールが付属してます(本体はlib2to3という標準モジュール群)。たとえば、エンコード引数つきのunicode関数でも、そのままエンコード引数が存在しないstr関数に変えるなど、変換も完璧ではないけれど、たいていのコードは3で動くようになります。
しかし、変換してしまうとそのコードは2系では動かなくなります。ということで、2系でも3でも両方で動くようなコードを書くのはどうすればいいか、を考えてみました。
両方で動くようにするには、ない機能を使うわけには行かないので、どうしても共通部分のみでかかざろうえません。なのでお勧めはできないのですけど、一応両方で動かす前提で書くことを考えてみました。
関数風にカッコつき呼び出しにする(ただしprintのみの一行で)
print(object)
文字列とバイト列のリテラル
もともとPython2でも、unicodeとstrはそれほど意識して識別していなくてもstrで大体はうまく動くのですが。それでもこの二つを区別して使う場合が出るので、対応しておきます。
# -*- coding: utf-8 -*- try: unicode # python2 def u(str): return str.decode("utf-8") def b(str): return str pass except: # python3 def u(str): return str def b(str): return str.encode("utf-8") pass
と定義して、以下のように使えば2も3も両方で同じ意味で動きます。
u("あいう") # 文字列 b("あいう") # バイト列
まずは、Python 2.5での相互変換のコード
# -*- coding: utf-8 -*- # Python 2.X str/unicode print repr("あいう") # byte stream literal print repr(u"あいう") # unicode string literal print repr(unicode("あいう", "utf-8")) # str to unicode print repr("あいう".decode("utf-8")) # str to unicode print repr(str(u"あいう".encode("utf-8"))) # unicode to str
Python3では、
# Python 3.0 bytes/str print(repr(b"abc")) # byte stream literal: only ASCII/8bit(\xXX) chars print(repr("あいう")) # unicode string literal print(repr(bytes("あいう", "utf-8"))) # str to bytes print(repr("あいう".encode("utf-8"))) # str to bytes print(repr(b"abc".decode("utf-8"))) # bytes to str
Python2でのstrとunicodeはPython3ではbytesとstrになってます。
もともとのstrは別物になって、別のものをstrにリネームしてるわけですが、Python3ではこういうタイプの変更がいくつかあります。
意味が変わった関数
Python2 | Python3 | 補足 |
---|---|---|
xrange | range | py3ではclass range |
range | 廃止 | py3版: list(range(...)) |
raw_input | input | |
input | 廃止 | py3版: eval(input(...)) |
上記のようにrangeやinputはもともとのものとは意味が変わってしまいました。
両方で動かすには、Python3にある語彙のみを、Python2互換の名前で使うことで両方で動かせます。
try: xrange except: xrange = range
もしくは、
try: xrange except: def xrange(*args, *kargs): return range(*args, **kargs) pass
標準モジュールのモジュール名の変更
中の実装は同じでもモジュール構造はだいぶ変更されてます。Python3では
- 大文字を使った名前のモジュールはない
- ディレクトリにまとめているモジュール整理が進んでる
例:
Python2 | Python3 | 補足 |
---|---|---|
thread | _thread | 隠蔽? |
Queue | queue | |
BaseHTTPServer | http.server | |
CGIHTTPServer | http.server | |
httplib | http.client | |
Cookie | http.cookies |
両方で動かすには、
try: import thread except: import _thread as thread
もしくは
try: from thread import start_new_thread except: from _thread import start_new_thread
消えたものを補う
Pythonで消えてしまったものもあるので、以下のように補いました
import operator try: operator.div except: operator.floordiv
余談ですが、div(/)はtruediv(/)とfloordiv(//)に分かれています。python2での/は、python3では//と同じ意味になり、Python3での/はtruedivの意味になりました(10 / 3 == 3.33...) 。python2でも//は同じ意味で使用できるので、こちらを使うのがいいようです。
generator/iterator
以前はgtor.next()だったのですが、next関数がbuiltinsにはいり、イテレータのnextメソッドの役目はgtor.__next__()に引き継がれました。sendやcloseはそのまま残ってます。send(None)がnext代わりになるのも同じでした。
exceptでの例外キャッチ変数
まず、互換性で残っていた文字列raiseはなくなりました。文字列をそのままraiseに使わないようにします。
構文で変数に割り当てずにsys.exc_info()を使います。
try: raise StopIteration except StopIteration: ex = sys.exc_info()[1] print(repr(ex))
Python2.5以前でのexceptで変数で捕らえる構文と、Python3の構文とは相互に非互換です。
# python2 try: raise StopIteration except StopIteration, ex: print ex
# python3 or python2.6 try: raise StopIteration except StopIteration as ex: print(repr(ex))
例:
PythonでLISPっぽい何かを作ってみた - ラシウラのlisp.pyを発展させたものに適用させてみました。