Python3とPython2で両方で動くコードを書こうとしてみた

Python3には、2系で作られたコードを3用に変換する案をパッチを生成する2to3というコマンドラインツールが付属してます(本体はlib2to3という標準モジュール群)。たとえば、エンコード引数つきのunicode関数でも、そのままエンコード引数が存在しないstr関数に変えるなど、変換も完璧ではないけれど、たいていのコードは3で動くようになります。

しかし、変換してしまうとそのコードは2系では動かなくなります。ということで、2系でも3でも両方で動くようなコードを書くのはどうすればいいか、を考えてみました。

両方で動くようにするには、ない機能を使うわけには行かないので、どうしても共通部分のみでかかざろうえません。なのでお勧めはできないのですけど、一応両方で動かす前提で書くことを考えてみました。

print

関数風にカッコつき呼び出しにする(ただしprintのみの一行で)

print(object)

文字列とバイト列のリテラル

もともとPython2でも、unicodeとstrはそれほど意識して識別していなくてもstrで大体はうまく動くのですが。それでもこの二つを区別して使う場合が出るので、対応しておきます。

スクリプトファイルの文字コードは必ずutf8にします。

# -*- 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))