note.nkmk.me

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

Date: 2018-05-14 / tags: Python, 文字列操作
このエントリーをはてなブックマークに追加

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

Python3の場合について、以下の内容を説明する。

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

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

文字列からバイト列への変換(エンコード)は文字列(str型)のメソッドencode()を使う。

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

unicode-escapeはPython特有のエンコーディング。Python2ではstring-escapeという名称だった。

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

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'を指定すればよい。

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エスケープシーケンスを含んだ文字列のままでOK。

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から自動判別してバイト列をデコードしている。

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

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事