2011 Python アドベントカレンダー のエントリです。Python 2 と Python 3 の差異を吸収するラッパ集として six というライブラリを触ってみました。
Python 3 に同梱されている 2to3 というツールは、ひとつの Python 2 のソースから、 Python 3 用のソースを自動生成します。したがって、もとの Python 2 用コードと Python 3 用コードのふたつのバージョンを持つことになります。
一方、six がやろうとしているのは、ひとつのソースコードを Python 2 と Python 3 からも呼び出せるようにすることです。提供されるのは、基本的な定数や、ラッパ関数です。
文字列リテラルのフェイク
Python 3 と言えば文字列です。
# coding: utf-8
print(u'うほ')
Python 3 ではこれはエラーになります。six.u() 関数は、文字列リテラルのフェイクになってくれるそうです。
# coding: utf-8
import six
print(six.u('うほ'))
実行してみましょう。
$ python2.7 foo.py
ãã»
$ python3.2 foo.py
うほ
えー!six.u() の引数は、 unicode-escape でエンコードされた文字列じゃないとだめだそうです。「うほ」の場合は "\\u3046\\u307b" です。これはあんまりじゃないかと。しかも、
$ python2.7 foo.py
うほ
$ python3.2 foo.py
\u3046\u307b
ええぇぇ。そりゃまあ、リテラル表記を関数で代用するんですから、大変だと思いますよ。
見なかったことにして、six.b() に行きましょう。これは b'…' のフェイクです。
x = six.b('AB')
print(x[0]+x[1])
$ python2.7 foo.py
AB
$ python3.2 foo.py
131
oh… このあたりの挙動は 石本さんのブログ で書かれていますが、リテラルのフェイクは難しいようです。
気をとりなおして別の関数を見ましょう。それは文字列なのか、それともバイト列なのかを知りたいとき、isinstance とかを使うわけです。が、Python 3 では、このあたりの型や継承関係が変わっています。
x = u'foo'
if isinstance(x, basestring):
print(x.encode('ascii'))
y = b'ABC'
if isinstance(y, str):
print(y.decode('ascii'))
Python 2 では、上のように書いたりしたんですが、Python 3 では basestring は存在しませんし、str はバイナリを表しません。そこで、six の text_type と binary_type を使います。
x = six.u('foo')
if isinstance(x, six.text_type):
print(x.encode('ascii'))
y = six.b('ABC')
if isinstance(y, six.binary_type):
print(y.decode('ascii'))
実行してみると…
$ python2.7 foo.py
foo ABC $ python2.7 foo.py b'foo' ABC
お、これはわりとちゃんと動きますね。text_type は unicode (py2) か、str (py3) のエイリアスになっています。binary_type は同じく str と bytes です。
辞書のイテレータ
Python 2 の iteritems, iterkeys, itervalues は、Python 3 では items, keys, values に名前が変わっています。そして、Python 2 の items, keys, values はなくなっています。
この差異を吸収するのが、 six.iteritems などの関数です。
for k,v in six.iteritems({'spam':99, 'ham':0}):
print(k,v)
$ python2.7 foo.py ('ham', 0) ('spam', 99) $ python3.2 foo.py ham 0 spam 99
辞書のメソッドを使わずに、six.iter* を使うことで、必ずイテレータを返すようになります。
表示が違うのは、print(k,v) の解釈の違いです。 Python 2 では、タプル (k,v) をプリントしています。一方、Python 3 では、k と v ふたつの引数をとって、それぞれをスペース区切りで表示します。
six には、これを吸収する関数もあります。
six.print_(1,2,3)
実行すると…
$ python2.7 foo.py
1 2 3
$ python3.2 foo.py
1 2 3
まとめ
言語仕様が変わって、オフィシャルに 2to3 が用意されるくらいですから、ラッパくらいでは吸収し切れない、というのがよく分かりました。個人的な感想は、2to3のほうが筋がいいんではないか、と思いました。
あしたは @nonNoise さんにバトンタッチです。よろしくお願いします。
2011-12-13 追記: pre タグがおかしかったのなど、諸々修正。blogspot って wysiwig エディタでコード書くのがいまいち。