Python: Unicodeのコードポイントとバイト列との変換

2018-03-23

旧ブログ

t f B! P L
コードポイントをバイト列に変更する方法をエンコーディングといいutf-8やutf-16などがあります。

前の関連記事:Python: Unicodeのコードポイントと文字列の変換

文字列→utf-8エンコーディング→16進数表示→バイト列→文字列

In [1]:
# 文字列をutf-8エンコーディングのバイト列にする。
# Unicode文字は\xエスケープで2桁ずつ16進数で表示される。
# バイト列内で2桁の16進数のASCII文字コードに一致する部分はASCII文字で表示される。
u8 = '3日Sun.'.encode()
u8
Out[1]:
b'3\xe6\x97\xa5Sun.'
ASCII文字のutf-8エンコーディングはASCII文字コードと一致するのでASCII文字でない文字だけ2桁の16進数で表示されています。
In [2]:
# バイト列を16進数表示の文字列にする。ASCII文字もすべて16進数表示になる。
h = u8.hex()
h
Out[2]:
'33e697a553756e2e'
In [3]:
# 16進数表示の文字列をバイト列に変換する。
# bytesは組み込みクラスでfromhex()はクラスメソッド、
b = bytes.fromhex(h)
b
Out[3]:
b'3\xe6\x97\xa5Sun.'
In [4]:
# バイト列をutf-8として文字列にデコードする。
b.decode()
Out[4]:
'3日Sun.'

文字列→utf-16エンコーディング→16進数表示→バイト列→文字列

In [5]:
# 文字列をutf-16エンコーディングのバイト列にする。
# バイト列内で2桁の16進数のASCII文字コードに一致する部分はASCII文字で表示される。
u16 = '3日Sun.'.encode("utf-16")
u16
Out[5]:
b'\xff\xfe3\x00\xe5eS\x00u\x00n\x00.\x00'
\xの次に2桁の16進数が続くはずですが、バイト列だとASCII文字コードに一致してしまうとASCII文字で表示されます。
正確な16進数表示をみたいときはhex()メソッドで16進数にしないといけません。
In [6]:
# バイト列を16進数表示の文字列にする。
h = u16.hex()
h
Out[6]:
'fffe3300e565530075006e002e00'
In [7]:
# 16進数の文字列をバイト列に変換する。
# bytesは組み込みクラスでfromhex()はクラスメソッド。
b = bytes.fromhex(h)
b
Out[7]:
b'\xff\xfe3\x00\xe5eS\x00u\x00n\x00.\x00'
In [8]:
# バイト列をutf-16として文字列にデコードする。
b.decode("utf-16")
Out[8]:
'3日Sun.'

サロゲートペアのコードポイントを取得する

サロゲートペアというのはUnicodeが16ビット(4桁の16進数、2バイト)から拡張するときに、16ビットを前提としたシステムへの影響を最小限にするために考えだされたものだそうです(Unicode - Wikipedia)。
なので、サロゲートペアは2桁の16進数を2つ組み合わせたものになっています。

Pythonではエスケープ\Uを使えば32ビット(8桁の16進数)のコードポイントが扱えるのでサロゲートペアを使う必要がありません。

サロゲートペアのコードポイントしかわからないときは、それを8桁の16進数のコードポイントに変換する必要があります。
In [9]:
s = "\ud83d\ude4f"  # サロゲートペアのUnicodeリテラル。
s
Out[9]:
'\ud83d\ude4f'
Pythonでは、サロゲートペアのUnicodeリテラルは文字に変換されないのでこれを文字に変換されるUnicodeリテラルにします。
In [10]:
# surrogatepassエラーハンドラをつけてutf-16でバイト列にエンコードする。
b = s.encode('utf-16', 'surrogatepass')
b
Out[10]:
b'\xff\xfe=\xd8O\xde'
In [11]:
d = b.decode('utf-16')  # utf-16として文字列にデコードする。
d
Out[11]:
'🙏'
これでサロゲートペアの文字が表示されるようになりました。
In [12]:
print(d.encode('unicode_escape').decode())  # 文字列をUnicodeリテラルにする。
\U0001f64f
これでこのサロゲートペアのコードポイントがU+1F64Fとわかりました。
In [13]:
u = "\U0001F64F"  # \Uでエスケープして8桁の16進数のUnicodeリテラルにする。
u
Out[13]:
'🙏'
utf-8やutf-32のエンコーディングでもsurrogatepassエラーハンドラが使えますが、utf-16以外ではうまく文字列にデコードできませんでした。
In [14]:
"\uD83C\uDF1F".encode('utf-16', 'surrogatepass').decode('utf-16')
Out[14]:
'🌟'
これで1行でサロゲートペアを文字列にできます。

print("\uD83C\uDF1F".encode('utf-16', 'surrogatepass').decode('utf-16').encode('unicode_escape').decode())

これで8桁の16進数のコードポイントを取得できます。

ノーブレークスペースを、Unicodeリテラル→文字→utf-8エンコーディング→16進数→バイト列→文字、に変換する。

In [15]:
# ノーブレークスペースのコードポイントはU+00A0
# ノーブレークスペースはグリフ(文字)がないので\xエスケープで16進数が表示される。
u = "\u00A0"
u
Out[15]:
'\xa0'
In [16]:
# utf-8エンコーディングでバイト列にする。
u8 = u.encode()
u8
Out[16]:
b'\xc2\xa0'
In [17]:
# バイト列を16進数表示の文字列にする。
h = u8.hex()
h
Out[17]:
'c2a0'
In [18]:
# 16進数表示の文字列をバイト列にする。
b = bytes.fromhex(h)
b
Out[18]:
b'\xc2\xa0'
In [19]:
# バイト列をutf-8としてデコードして文字列にする。
s = b.decode()
s
Out[19]:
'\xa0'
In [20]:
print(s.encode('unicode_escape').decode()) 
\xa0
もとの文字がUnicode文字ではなくASCII文字だけなので\u00a0にはなりませんでした。

ノーブレークスペースはUTF-8の半角スペースには2種類あるででてきたものです。

参考にしたサイト


Unicode - Wikipedia
サロゲートペアは後方互換性のために編み出されたものののようです。

Unicode HOWTO — Python 3.5.3 ドキュメント
PythonでのUnicodeの扱いの解説。

How to work with surrogate pairs in Python? - Stack Overflow
PythonでサロゲートペアをUnicodeリテラルに変換する方法。

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ