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

\u3042のように\uと4桁の16 進数からなるUnicodeエスケープシーケンスを含む文字列・バイト列を相互に変換する方法を説明します。 Python3の場合について、以下の内容を説明します。

文字列をUnicodeエスケープされたバイト列に変換(エンコード) Unicodeエスケープされたバイト列を文字列に変換(デコード) Unicodeエスケープされた文字列を通常の文字列に変換 通常の文字列をUnicodeエスケープされた文字列に変換 Unicodeエスケープシーケンスをprint()でそのまま出力 Unicodeエスケープされた文字列を含むファイルを読み込み JSONのUnicodeエスケープ

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

文字列からバイト列への変換(エンコード)は文字列(str型)のメソッドencode()を使います。 Unicodeエスケープされたバイト列をエンコードする場合は、第一引数encodingに'unicode-escape'を指定します。アンダースコアでなくハイフンの'unicode_escape'でも問題ありません。 unicode-escapeはPython特有のエンコーディング。Python2ではstring-escapeという名称だった。

7.2. codecs Python 特有のエンコーディング — codec レジストリと基底クラス — Python 3.6.5 ドキュメント

s = 'あいうえお'

b = s.encode('unicode-escape')

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

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

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

バイト列から文字列への変換(デコード)はバイト列(bytes型)のメソッドdecode()を使います。 エンコードと同じく第一引数encodingに'unicode-escape'を指定するとUnicodeエスケープされたバイト列が元の文字列に戻る。

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'なので省略しても同じ結果。

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'>

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

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でエスケープされます。

  1. 組み込み関数 ascii() — Python 3.6.5 ドキュメント

ascii()は先頭と末尾に引用符'を含んだ文字列(\uXXXXの6文字分とあわせて8文字)を返します。

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)は文字列(str型)中にそのまま記述すると対応する文字一文字分として扱われ、print()では対応する文字が出力されます。

print('\u3042')
# あ

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

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

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

print('\\u3042')
# \u3042

print(r'\u3042')
# \u3042

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

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

\u3042\u3044\u3046\u3048\u304aという文字列のテキストファイルを読み込む。

open()の引数encodingを設定しないとそのまま読み込まれる。

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

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エスケープ

PythonでUnicodeエスケープに遭遇しがちなのが、Web APIでjsonなどを取得する場合です。 標準ライブラリのurllib.requestモジュールの関数urllib.request.urlopen()はバイト列(bytes型)を返します。 Unicodeエスケープされたバイト列をdecode()メソッド文字列に変換(デコード)する場合、第一引数encodingに'utf-8'を指定すると(引数を省略した場合も'utf-8')、Unicodeエスケープシーケンス(\uXXXX)を含んだ文字列となります。 上で説明したように、第一引数encodingに'unicode-escape'を指定すればOKです。

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

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

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

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

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

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

cpython/init.py at 3.6 · python/cpython

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

Last Updated: 6/26/2019, 10:34:03 PM