銀月の符号

Python 使い見習いの日記・雑記

Python 2 と Python 3 のユニコード文字列、バイト列の違いメモ

Python 3 の数値をバイト列に変換する方法は bytes([i])*1。いままでとあきらかに違うことを知ったのが発端。

>>> bytes([97])
b'a'

ユニコード文字列、バイト列の違いを調査開始。

型の名称とリテラル表記

Python 2.6 は新旧両方の書き方ができるものも。

Python 2.5以前 Python 2.6 Python 3.0 and 3.1
ユニコード文字列型 unicode unicode str
バイト列型 str str or bytes*2 bytes
可変バイト列型 (なし)*3 bytearray bytearray
ユニコードリテラル u'a' u'a'*4 'a'
バイトリテラル 'a' 'a' or b'a'*5 b'a'

メソッド

メソッド関連は大差ない。

Python 3 では Python 2 の string.maketrans とそっくりのスタティックメソッド maketrans がバイト列型に加わっている。そしてユニコード文字列型にも maketrans スタティックメソッドが追加された。これはユニコード文字列型の translate に渡せる辞書を返す。

>>> str.maketrans('abc', 'ABC', 'd')  # Python 3 の新しい maketrans
{97: 65, 98: 66, 99: 67, 100: None}
>>> 'abcde'.translate(_)
'ABCe'

Python 2 で同じことをするための関数を作るとこんな感じ? Objects/unicodeobject.c を読んだり、動かしてみたりして作成してみた。

from itertools import izip

def maketrans(*x):
    arg_length = len(x)

    result = {}

    if arg_length in (2, 3):
        old_chars = x[0]
        new_chars = x[1]
        none_chars = u''
        if arg_length == 3:
            none_chars = x[2]

        for chars in (new_chars, none_chars):
            if not isinstance(chars, unicode):
                raise TypeError(u'must be %s, not %s' % (
                    unicode.__name__, chars.__class__.__name__))

        if not isinstance(old_chars, unicode):
            raise TypeError(
                    u'first maketrans argument must '
                    u'be a string if there is a second argument')

        if len(old_chars) != len(new_chars):
            raise ValueError(
                u'the first two maketrans arguments must have equal length')

        # create entries for translating chars
        # in old_chars to those in new_chars
        for old_char, new_char in izip(old_chars, new_chars):
            result[ord(old_char)] = ord(new_char)
        # create entries for deleteing chars in none_chars
        for none_char in none_chars:
            result[ord(none_char)] = None
    elif arg_length == 1:
        # x[0] must be a dict
        if not isinstance(x[0], dict):
            raise TypeError(
                    u'if you give only one argument '
                    u'to maketrans it must be a dict')
        # copy entries into the new dict, converting string keys to int keys
        for old_char, new_char in x[0].iteritems():
            if isinstance(old_char, unicode):
                # convert string keys to integer keys
                if len(old_char) != 1:
                    raise ValueError(
                            u'string keys in translate '
                            u'table must be of length 1')
                old_char = ord(old_char)
            elif isinstance(old_char, (int, long)):
                # just keep integer keys
                pass
            else:
                raise TypeError(
                    u'keys in translate table must be strings or integers')

            result[old_char] = new_char
    elif arg_length < 1:
        raise TypeError(u'maketrans() takes at least 1 argument (0 given)')
    else:
        raise TypeError(
            u'maketrans() takes at most 3 argument (%d given)' % arg_length)

    return result

また、Python 3 では文字列型の decode メソッド、バイト列型の encode メソッドは削除されている。

数値との相互変換

chr 関数が返すものが Python 2 と Python 3 で変わっている。というか Python 2 の chr が削除され、 unichr が chr に名前を変えられたというべきなのか。

Python 2 Python 3
ユニコードを数値に ord(s) ord(s)
数値をユニコード unichr(i) chr(i)
バイトを数値に ord(b) ord(b), iter(b)
数値をバイトに chr(i) bytes([i])

bytes([i]) と Python 2 の chr の違いは複数の値を一度に変換できるというもの。わざわざイテレータブルを要求するくらいだからできないと変か。Python 2 の chr は整数を受け取るのに対し、 bytes は整数を返すイテレータブルを受け取るという違いがある。このため Python 3 にて数値ひとつをバイトにするには bytes([i]) のようにリストを用意する必要がある。かわりに複数の数値をバイトにする際、 for 文、リスト内包、ジェネレータ式などを持ち出さなくてもよくなった。

>>> ''.join(chr(i) for i in [97, 98, 99]) # Python 2
'abc'
>>> bytes([97, 98, 99]) # Python 3 なら複数でもいける
b'abc'

バイトを数値に直すには ord だが、 Python 3 の bytes 型のイテレータは数値を返すのを利用することもできる。

>>> ord(b'a')
97
>>> list(b'abc')
[97, 98, 99]
>>> for i in b'abc':
...   print(i)
...
97
98
99
>>> print(*b'abc')
97 98 99

2009/10/31 追記。インデックスアクセスしても数値が返る。

>>> b'a'[0]
97
>>> b'abc'[0]
97

2to3.py はどう解釈するの?

2to3.py の挙動も確認してみる。

オリジナル 2to3変換結果
unichr(i) chr(i)
chr(i) chr(i)
unicode str
str str
u'a' 'a'
'a' 'a'
b'a' b'a'

chr はそのままなのか。そして str や 'a' もそのまま。 2to3.py はバイト列として扱って欲しいものをユニコード文字列に直してしまうかもしれない。覚えておこうっと。でも、テキスト処理用のアプリ・ライブラリには都合がよいかも。

ユニコード文字列は u'a' 、バイト列は b'a' と書くようにすると Python 2.6 以降で動き、 かつ 2to3.py で Python 3 用に誤爆なしで変換できるコードになる。しかし Python 2.5 以前で動かないデメリットを上回るかどうかは不明。

*1:リストである必要はなく、数値を返すイテレータブルオブジェクトであればよい。

*2:Python 2.6 の bytes は str のエイリアスにすぎない。なので bytes([i]) とかいても Python 3 と同じ動作はしない。

*3:array.array('c') で得られるオブジェクトが近いものではある。しかし、こちらのインターフェースは独自のものになっている。

*4:from __future__ import unicode_literals 時は 'a' or u'a'

*5:from __future__ import unicode_literals 時は b'a' のみ