LoginSignup
4
2

More than 5 years have passed since last update.

UCS-2 か UCS-4 かによって len や unichr の戻り値が変わることがある

Last updated at Posted at 2014-12-10

Mac OS X の標準の Python 2 は UCS-2 でビルドされているため、Linux ディストリービョンで広く採用されている UCS-4 の場合と比べて、標準関数の len や unichr が返す値が異なることがある。

UCS-2 のビルドオプションでのふるまい

ビルドオプションが UCS-2 である場合、U+10000 とそれ以降の文字が含まれる場合、len をそのまま使って文字数を求めることはできない。homebrew で導入した場合も USC-2 でビルドされる。

ビルドオプションに UCS-2 が指定されたかどうかを調べるには sys.maxunicode の値を使う。

>>> import sys
>>> 0xFFFF == sys.maxunicode
True

次の文字列 (U+20BB7 U+91CE U+5BB6) に len を適用すると、戻り値は4になる。

>>> str = u'?野家'
>>> 4 == len(str)
True

U+20BB7 の内部表現はサロゲートペアの U+D842 U+DFB7 となる。

>>> 0xD842 == ord(str[0])
True
>>> 0xDFB7 == ord(str[1])
True

UCS-2 を考慮して文字数を求める

上位サロゲートの範囲は U+D800 から U+DBFF までであることを踏まえて、文字数を求めてみよう。コードをかんたんにするために、上位サロゲートもしくは下位サロゲートが孤立する場合は考慮しない。UCS-4 であれば、for ループを使うことができる。

# -*- coding: utf-8 -*-

import sys

def utf8_len(str):

    length = 0

    if sys.maxunicode > 0xFFFF:
        for c in str:
            length += 1

        return length

    code_units = len(str)
    pos = 0
    cp = -1

    while pos < code_units:

        cp = ord(str[pos])
        length += 1

        if cp > 0xD7FF and 0xDC00 > cp:
            pos += 2
        else:
            pos += 1

    return length

先ほどの文字列を再度試してみよう。

str = u'?野家'
print(3 == utf8_len(str))

練習として、少しコードを修正して、1文字ずつコールバックを適用する関数を定義してみよう。

# -*- coding: utf-8 -*-

import sys

def utf8_each_char(str, func):

    if sys.maxunicode > 0xFFFF:
        for c in str:
            func(c)
    else:
        code_units = len(str)
        pos = 0
        buf = ''
        cp = -1

    while pos < code_units:
        buf =str[pos]
        cp = ord(buf)

        if cp > 0xD7FF and 0xDC00 > cp:
            buf += str[pos+1]
            func(buf)
            pos += 2
        else:
            func(buf)
            pos += 1

1文字ずつ表示してみよう。ラムダ式で print を使うには print_function をファイルの冒頭でインポートする必要がある。

from __future__ import print_function

str = u'?野家'
f = lambda c: print(c)
utf8_each_char(str, f)

UCS-2 を考慮してコードポイントから文字を生成する

USC-2 の制約はコードポイントの整数から文字を生成する unichr も受け、0x10000 とそれ以降の整数を受けつけない。

>>> unichr(0x20BB7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: unichr() arg not in range(0x10000) (narrow Python build)

Unicode エスケープシーケンスは UCS-2 の影響を受けない。


>>> print(u"\U00020BB7")
?

UCS-2 の制約を考慮したユーザー関数を定義すると次のようになる。

# -*- coding: utf-8 -*-

import sys

def utf8_chr(cp):
    if 0xFFFF < sys.maxunicode or cp < 0x10000:
        return unichr(cp)

    cp -= 0x10000
    high = cp >> 10 | 0xD800
    low = cp & 0x3FF | 0xDC00

    return unichr(high) + unichr(low)

print(utf8_chr(0x20BB7))
print(utf8_chr(0x91CE))
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2