https://docs.python.org/ja/3.13/howto/unicode.html

Unicode ではすべての文字 (Character) を対象にしており、各文字に codepoint を設定しています。これは各文字に一意の数字を割り当てるもの、といえます。

265E    '♞'; BLACK CHESS KNIGHT
265F    '♟'; BLACK CHESS PAWN

U+265E は Unicode 上のコードポインタが 265E であることを表しています。 また、 0x265e = 9,822 が '♞'; BLACK CHESS KNIGHT になっています。

エンコーディング

Unicode のコードポイントの列は codeunit 列として表され、 8-bit のバイト列にマップされます。

Imgur 上記の画像では 32bit = 4 byte で表されており、 P = 0x00000050 = 80 です。 #little-endian で扱われるため 0x50000000 となります。

ただし、多くの文書は 127 ~ 255 の alphabet で表せるため 0x00 の 1byte で表現できるはずですが、上記の例では 1 文字 4 byte となっています。 これだと効率が悪いです。

そのため、 UTF-8 という Unicodde の encoding が使われます。 UTF-8 の仕組みは

  1. コードポイントが 128 未満であれば、対応する 1 バイトで表現する
  2. コードポイントが 128 以上であれば、 128 ~ 255 までのバイトからなる 2, 3, 4 バイトのシーケンスで表現する

です。 UTF-8 という 8 bit を基本としたエンコーディングであれば最小限のバイトで同一の英文列を表現できます。

Python における Unicode

Python ソースコードのデフォルトエンコーディングは UTF-8 です。 そして、 python の str は Unicode で表されます。

ord を使うと codepoint に変換でき、また chr で Unicode 文字に変換できます。 ✅️ = 9989 = 0x2705 = U+2705 です。

>>> ord('✅')
9989
>>> chr(9989)
'✅'

str.encode() メソッドによって、 Unicode 文字列を指定された encoding でエンコードして bytes に変換します。 str 型から byte 型へエンコーディングしたうえで変換するのですね。

>>> txt='ab✅️'
>>> txt.encode('utf-8')
b'ab\xe2\x9c\x85\xef\xb8\x8f'
>>> txt.encode('utf-16')
b"\xff\xfea\x00b\x00\x05'\x0f\xfe"
>>> txt.encode('utf-32')
b"\xff\xfe\x00\x00a\x00\x00\x00b\x00\x00\x00\x05'\x00\x00\x0f\xfe\x00\x00"

utf-8 のときは b'ab\xe2\x9c\x85\xef\xb8\x8f' となっています。これは

  • a = U+0061 = b’a’ (0x61)
  • b = U+0062 = b’b’ (0x62)
  • ✅️ = U+2705 = \xe2\x9c\x85 (0xE29C85)
  • ” (絵文字制御専用文字) = U+FE0F = \xef\xb8\x8f

よって b'ab\xe2\x9c\x85\xef\xb8\x8f' で表されます。

utf-16 のときは

  • BOM = リトルエンディアンであることを表す = \xff\xfe
    • 下位ビットから上位ビットへ
  • a = U+0061 = 0x61 0x00 = a\x00
  • b = U+0062 = 0x62 0x00 = b\x00
  • ✅️ = U+2705 = 0x05 0x27 = \x05’
  • ” = U+FE0F = \x0f\xfe

となります。

U+2705 が \xe2\x9c\x85 になる理由

U+2705 が \xe2\x9c\x85 になる理由は以下です。

U+2705 は 3 byte であり, 3 byte は

  • 1 byte = 1110xxxx
  • 2 byte = 10xxxxxx
  • 3 byte = 10xxxxxx というフォーマットで表されます。これは UTF-8 で定められたルールです。

0xE2, 0x9c, 0x85 を 2 進数に純粋に変換すると

  • 0xE2 = 11100010
  • 0x9C = 10011100
  • 0x85 = 10000101 になります。

そして、 1, 2, 3byte 目のルール 1110xxxx 10xxxxxx 10xxxxxx で 0xE2, 0x9c, 0x85 を抜き出すと

  • 0010
  • 011100
  • 000101

となります。 0010 011100 000101 (16bit) を 16 進数に直すと

  • 0010 = 2
  • 0111 = 7
  • 0000 = 0
  • 0101 = 5

となり、2705 に一致します。

U+2705 からバイトに変換したい場合は

  • 2705 → 0010 0111 0000 0101 に変換する
    • 0x0800 ~ 0xFFFF の範囲内であるため 3 byte で表現する方針になる
  • 1110xxxx, 10xxxxxx, 10xxxxxx のテンプレートに割り当てる
    • 1byte 目: 1110 0010 (上位 4bit) = 0xE2
    • 2byte 目: 10 0111100 = 0x9C
    • 3byte 目: 10 000101 (下位 6 bit) = 0x85

よって、\xE2\x9C\x85 となります。