PythonでUnicodeエスケープされた文字列・バイト列を変換

Modified: | Tags: Python, 文字列, Unicode

Pythonにおいて、\u3042のように\uと4桁の16進数からなるUnicodeエスケープシーケンスを含む文字列(str)・バイト列(bytes)を相互に変換する方法を説明する。

Unicodeエスケープシーケンスは文字化けしたように見えるが、適切な処理を行うことで正しい文字列に変換できる。

Unicodeコードポイント(文字コード)と文字を相互に変換する方法については以下の記事を参照。

文字列をUnicodeエスケープされたバイト列に変換(エンコード)

文字列をバイト列に変換(エンコード)するには、encode()メソッドを使う。

第一引数encoding'unicode-escape'を指定すると、Unicodeエスケープされたバイト列にエンコードされる。ハイフンではなくアンダースコアの'unicode_escape'でもよい。

s = 'あいうえお'
b = s.encode('unicode-escape')
print(b)
# b'\\u3042\\u3044\\u3046\\u3048\\u304a'

print(type(b))
# <class 'bytes'>

Unicodeエスケープされたバイト列を文字列に変換(デコード)

バイト列を文字列に変換(デコード)するにはdecode()メソッドを使う。

第一引数encoding'unicode-escape'または'unicode_escape'を指定すると、Unicodeエスケープされたバイト列が元の文字列に戻る。

print(b)
# b'\\u3042\\u3044\\u3046\\u3048\\u304a'

s_from_b = b.decode('unicode-escape')
print(s_from_b)
# あいうえお

print(type(s_from_b))
# <class 'str'>

Unicodeエスケープされた文字列を通常の文字列に変換

Unicodeエスケープされたバイト列をutf-8でデコードすると、Unicodeエスケープされたままの文字列に変換される。第一引数encodingのデフォルト値は'utf-8'なので省略しても同じ結果。

print(b)
# b'\\u3042\\u3044\\u3046\\u3048\\u304a'

s_from_b_error = b.decode('utf-8')
print(s_from_b_error)
# \u3042\u3044\u3046\u3048\u304a

print(type(s_from_b_error))
# <class 'str'>

このようなUnicodeエスケープされた文字列は、encode()でバイト列に変換してから再度decode()で文字列に変換すると、通常の文字列に戻る。

s_from_s = s_from_b_error.encode().decode('unicode-escape')
print(s_from_s)
# あいうえお

print(type(s_from_s))
# <class 'str'>

標準ライブラリのcodecsモジュールを使って直接変換することも可能。

import codecs

s_from_s_codecs = codecs.decode(s_from_b_error, 'unicode-escape')
print(s_from_s_codecs)
# あいうえお

print(type(s_from_s_codecs))
# <class 'str'>

なお、ここでは説明のためにUnicodeエスケープされた文字列を作成したが、本来はUnicodeエスケープ(\u)が残らないようにしておくべき。大元の処理(バイト列からのデコード)を修正できるのであればそちらを修正したほうがいい。

通常の文字列をUnicodeエスケープされた文字列に変換

文字列のUnicodeエスケープシーケンス(\uXXXX)を確認するには、組み込み関数ascii()を使う。全角文字などの非ASCII文字が\uでエスケープされる。

ascii()は先頭と末尾に引用符'を含んだ文字列を返す。

s_ascii = ascii('あ')
print(s_ascii)
# '\u3042'

print(type(s_ascii))
# <class 'str'>

print(s_ascii[0])
# '

print(s_ascii[-1])
# '

print(len(s_ascii))
# 8

以下の文字列と等価。

print(ascii('あ') == "'\\u3042'")
# True

引用符を取り除きたい場合はスライスを使う。

s_unicode_escape = ascii('あ')[1:-1]
print(s_unicode_escape)
# \u3042

print(type(s_unicode_escape))
# <class 'str'>

print(s_unicode_escape == '\\u3042')
# True

Unicodeエスケープシーケンスをprint()でそのまま出力

Unicodeエスケープシーケンス(\uXXXX)は文字列リテラル中にそのまま記述すると対応する文字一文字分として扱われ、print()では対応する文字が出力される。

print('\u3042')
# あ

print(len('\u3042'))
# 1

print('\u3042' == 'あ')
# True

そのまま出力したい場合は、バックスラッシュを\\で表すか、エスケープシーケンスを無視するraw文字列を使う。

print('\\u3042')
# \u3042

print(r'\u3042')
# \u3042

print(len(r'\u3042'))
# 6

Unicodeエスケープされた文字列を含むファイルを読み込み

Unicodeエスケープされた文字列を含むファイルを読み込むには、open()の引数encodingを設定する。

\u3042\u3044\u3046\u3048\u304aという文字列が書き込まれたテキストファイルを例とする。

デフォルトではそのまま読み込まれる。

with open('data/src/unicode_escape.txt') as f:
    s = f.read()
    print(s)
    print(type(s))
    print(len(s))
# \u3042\u3044\u3046\u3048\u304a
# <class 'str'>
# 30

open()の引数encoding'unicode-escape'を指定すると対応する文字列に変換される。

with open('data/src/unicode_escape.txt', encoding='unicode-escape') as f:
    s = f.read()
    print(s)
    print(type(s))
    print(len(s))
# あいうえお
# <class 'str'>
# 5

JSONのUnicodeエスケープ

Unicodeエスケープに遭遇しがちなのが、Web APIでjsonなどを取得する場合。

標準ライブラリのurllib.requestモジュールの関数urllib.request.urlopen()はバイト列を返す。

Unicodeエスケープされたバイト列をdecode()メソッドで文字列に変換(デコード)する場合、デフォルトではUnicodeエスケープシーケンス(\uXXXX)を含んだ文字列となる。

上で説明したように、第一引数encoding'unicode-escape'を指定すればよい。

b_json = b'{"a": "\u3042"}'
print(b_json)
# b'{"a": "\\u3042"}'

print(b_json.decode())
# {"a": "\u3042"}

print(b_json.decode('unicode-escape'))
# {"a": "あ"}

jsonモジュールのloads()関数

標準ライブラリのjsonモジュールのloads()関数を使ってJSON形式の文字列を辞書(dict)に変換する場合は、Unicodeエスケープシーケンスを含んだ文字列のままでよい。

loads()関数の内部でUnicodeエスケープシーケンスを変換してくれる。

import json

s_json = '{"a": "\\u3042"}'
print(s_json)
# {"a": "\u3042"}

print(json.loads(s_json))
# {'a': 'あ'}

print(type(json.loads(s_json)))
# <class 'dict'>

バージョン3.6からはloads()の引数にバイト列を指定できるようになったので、Unicodeエスケープされたバイト列もそのまま指定可能。

b_json = b'{"a": "\u3042"}'
print(b_json)
# b'{"a": "\\u3042"}'

print(json.loads(b_json))
# {'a': 'あ'}

print(type(json.loads(b_json)))
# <class 'dict'>

内部でdetect_encoding()という関数が定義されており、エンコーディングをutf-8, utf-16, utf-32から自動判別してバイト列をデコードしている。

utf-8, utf-16, utf-32以外でエンコードされたバイト列の場合は、loads()に直接渡すのではなく、decode()メソッドでエンコーディングを指定してデコードする必要があるので注意。

jsonモジュールについての詳細は以下の記事を参照。

関連カテゴリー

関連記事