たにしきんぐダム

プログラミングやったりアニメやゲーム見たり京都に住んだりしてます

Pythonでの文字コードの取り扱い

定期的にこの手の記事が上がってる気がするけどあげていく。

unicode型とかstr型とかいう言い方するとわかりにくいけど、データの実体がどういうものかわかってれば理解しやすいよね。

参考

Unicode

Unicode では全ての文字にID(コードポイント)(0 ~ 0x10FFFF)をふっている。コードポイントを表す時は U+{16進数} と書く。

  • ord()により、ある文字に対応するコードポイント(の10進数表記を得ることができる)
  • chr()により、あるコードポイント(の10進数整数)から対応する文字を得ることができる
>>> ord("A")
65
>>> hex(ord("A"))
'0x41'
>>> chr(ord("A"))
'A'

ちなみに各コードポイントは文字に必ずしも割り当てられているわけではない。サロゲートコードポイントなどがその一例だが今回はその説明については省略する。

文字符号化方式

Unicodeにより各文字にはそれぞれコードポイントが割り当てられいる、それではこれをコンピュータによってどのように表現するかであるが、 UTF-8 UTF-16 UTF-32 といった符号化方式が存在する。それぞれ 8bit 16bit 32bit を単位とする符号化方式である。

UTF-16

16bitで表すことのできる U+0000 ~ U+FFFF までは1符号単位(16bit)により表す。

16bitだけで表現できない U+10000 ~ U+10FFFF はサロゲートペアという二つの符号単位の塊(16bit * 2)により表現する。

サロゲートペアを構成するコードポイントはサロゲートコードポイントと呼ばれる、サロゲートコードポイントはさらに以下のように分けることができて

上位サロゲートと下位サロゲートによりサロゲートペアを構成する

UTF-8

8bitを符号単位とする符号化方式 1byteで表現できる U+0000 ~ U+007F は1byte 2byteで表現できる U+0080 ~ U+07FF は2byte というようにコードポイントによって異なる長さのバイトシーケンスによりいい感じに符号化を行う

ASCII

ASCIIは7bitにより表される各整数(0~127)に対して文字を割り当てた符号化方式 ord() により得られる整数(0~127)は Unicode code point でありかつ ascii code でもある。 unicode code point 0~127 の文字に関しては Unicode と ascii は互換性がある。

Python3 における str型 と bytes型

ここまできたらPythonにおけるunicode型とstr型が理解できると思う。

Python3では str型Unicode文字(Unicode code point のシーケンスで表された文字)を実体としており、 bytes型utf-8utf-16やasciiといった符号化方式でエンコードされたバイトシーケンスが実体としている。

>>> type("🍣")
<class 'str'>
>>> hex(ord("🍣"))
'0x1f363'  # Unicode code point
>>> "🍣".encode('utf-8')  # unicode code point を utf-8 でエンコード
b'\xf0\x9f\x8d\xa3'  # バイトシーケンス
>>> type(b'\xf0\x9f\x8d\xa3')
<class 'bytes'>
>>> b'\xf0\x9f\x8d\xa3'.decode('utf-8')  # バイトシーケンスをutf-8でエンコードされたものだと思い込んで、unicode code point にデコード
'🍣'

Python3でのデフォルトエンコーディングUTF-8 バイトシーケンス<--->Unicode code point のエンコード/デコードはデフォルトではutf-8で行われる

>>> import sys
>>> sys.getdefaultencoding()
'utf-8'

ファイルの先頭で以下のようにエンコーディングを指定することもできる

# coding: -*- <encoding name> -*-

UnicodeDecodeError/UnicodeEncodeError

バイトシーケンス => Unicode code point への変換を考えてみる

# ascii互換の文字(code point 0~127 の文字)は utf8 バイトシーケンス を
# ascii バイトシーケンスだと思って decode しても unicode code point が得られる
# 0~127 の文字はutf-8でも 1byte だし (128~255 はasciiではない...)
>>> 'A'.encode('utf-8').decode('ascii')
'A'
# ascii非互換の文字のutf-8バイトシーケンス取得
>>> 'あ'.encode('utf-8')
b'\xe3\x81\x82'
# これをasciiだと思ってDecodeを試みる
# \xe3 などは 0~127 外(非ascii) なのでDecodeに失敗する(UnicodeDecodeError)
>>> 'あ'.encode('utf-8').decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position0: ordinal not in range(128)

次に Unicode code point => ascii バイトシーケンスへの変換を考えてみる

# Unicode code point (str型) をバイト列(ascii encode)に変換する際
# コードポイントが0~127ならOK
# それ以外ならascii encode に失敗 (UnicodeEncodeError)
>>> 'A'.encode('ascii')
b'a'
>>> 'あ'.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character '\u3042' in position 0: ordinal not in range(128)

補足 Python2 の str型 と unicode

Python2におけるstr型はPython3のそれとは違う

  • Python2のstr型 = Python3のbytes型 = バイトシーケンス
  • Python2のunicode型 = Python3のstr型 = Unicode code point シーケンス

混乱するね

Python3では 'a' といった表記の文字列は Unicode code point で表されていたが、 Python2では 'a' はバイトシーケンスで表される。 Python2でユニコードコードポイントを得るには u'a' このように書く

>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=13, releaselevel='final', serial=0)
>>> type('a')
<type 'str'>  # ここで言うstrはバイトシーケンスであることに注意
>>> type(u'a')
<type 'unicode'>  # これは unicode code point

Python2 のデフォルトエンコーディング

Python2 でのデフォルトエンコーディングはascii

>>> import sys
>>> sys.getdefaultencoding()
'ascii'

なので

# python3 ではこれは utf-8 エンコード、デコードだが
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=1, releaselevel='final', serial=0)
>>> 'あ'.encode()
b'\xe3\x81\x82'
>>> b'\xe3\x81\x82'.decode()
'あ'
>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=13, releaselevel='final', serial=0)
# unicode code point を ascii エンコードしようとする、「あ」はasciiの範囲外
>>> u'あ'.encode()
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3042' in position 0: ordinal in range(128)