Pythonのバイト列で驚いたこと2013年11月29日 18:08

ユニコード符号位置からShift_JIS-2004とEUC-JIS-2004を求める必要があって、codecsエンコーダ関数の動作を対話モードで試していたときのことです。

まずは必要なモジュールをimportしてcodecsエンコーダ関数を取得します。

>>> import codecs, struct
>>> enceuc = codecs.getencoder('euc_jis_2004')
>>> encsjis = codecs.getencoder('shift_jis_2004')
>>>

「ア」U+30A2(KATAKANA LETTER A)(JIS8bit 0x2522)をEUC-JIS-2004で符号化してみます。

>>> c = chr(int('30a2', 16))
>>> euc = enceuc(c)
>>> euc
(b'\xa5\xa2', 1)
>>>

EUC-JIS-2004では、16進表記でa5a2です。次はShift_JIS-2004で符号化してみます。

>>> sjis = encsjis(c)
>>> sjis
(b'\x83A', 1)
>>>

(b'\x83\x41', 1)を期待していたのに、一瞬ギョッとしました。変換テーブルが間違っているのかと思ってしまいました。バイト列なのだから当然16進表記で見せてくれるものと思いこんでいたのです。
Python 3.3 documentation(http://docs.python.jp/3.3/)の「Python 標準ライブラリ」「4.8. バイナリシーケンス型」のところに、
「bytes リテラルでは (ソースコードのエンコーディングに関係なく) ASCII文字のみが許可されています。127より大きい値を bytes リテラルに記述する場合は適切なエスケープシーケンスを書く必要があります。」
と書いてあることから推測すると、Pythonではこれが当然ということなのでしょう。ASCII文字の「A」は16進表記では「41」です。

エンコードの結果はタプルで返ってくるので、実際のスクリプトでは16進表記の文字列に変換することにしました。

>>> ww = struct.unpack('BB', sjis[0])
>>> sjisw = hex(ww[0] * 256 + ww[1])
>>> sjisw
'0x8341'
>>>

これで良さそうです。

ちなみに、わたしがPythonを使うのは「好み」ではなく、ユニコードで結合文字を使って表現する25文字を含めて、JISX0213に完全対応していることに敬意を表しているからです。 例えば、「か゚」(U+304B+309A)(JIS8bit 0x2477)をShift_JIS-2004で符号化するとこうなります。

>>> c1 = chr(0x304b)
>>> c2 = chr(0x309a)
>>> cw = c1 + c2
>>> sjis = encsjis(cw)
>>> sjis
(b'\x82\xf5', 2)