Google App Engine は Python 2.5.2 を使用しており、明らかに UCS4 が有効になっています。ただし、GAE データストアは内部で UTF-8 を使用します。したがって、u'\ud834\udd0c' (長さ 2) をデータストアに格納すると、それを取得すると、'\U0001d10c' (長さ 1) が得られます。文字列を格納する前後で同じ結果が得られるように、文字列内の Unicode 文字の数を数えようとしています。そのため、文字列を受信したらすぐに (u'\ud834\udd0c' から '\U0001d10c' に) 正規化してから、その長さを計算してデータストアに入れようとしています。UTF-8にエンコードしてから再度デコードできることはわかっていますが、もっと簡単で効率的な方法はありますか?
2 に答える
UTF-8にエンコードしてから再度デコードできることはわかっています
はい、これは「UCS-4 文字列の UTF-16 サロゲート」入力がある場合の問題を解決するための通常のイディオムです。しかし、Mechanical snail が言ったように、この入力は形式が正しくないため、生成されたものを優先して修正する必要があります。
もっと簡単で効率的な方法はありますか?
まあ...次のような正規表現を使用して手動で行うことができます:
re.sub(
u'([\uD800-\uDBFF])([\uDC00-\uDFFF])',
lambda m: unichr((ord(m.group(1))-0xD800<<10)+ord(m.group(2))-0xDC00+0x10000),
s
)
確かにもっと簡単ではありません...実際にもっと効率的かどうかについても疑問があります!
残念ながら、3.3 より前のバージョンでの CPython インタープリターの動作は、「狭い」または「広い」Unicode サポートで構築されているかによって異なります。したがって、 への呼び出しなどの同じコードlen
が、標準インタープリターの異なるビルドでは異なる結果になる可能性があります。例については、この質問を参照してください。
「ナロー」と「ワイド」の違いは、「ナロー」インタープリターは内部に 16 ビット コード ユニット (UCS-2) を格納するのに対し、「ワイド」インタープリターは内部に 32 ビット コード ユニット (UCS-4) を格納することです。コードポイントU+10000 以上 (基本多言語面の外側) は、len
「狭い」インタープリターで 2 になります。これは、(サロゲートを使用して) それらを表現するために 2 つの UCS-2 コード単位len
が必要であるためです。「ワイド」ビルドでは、非 BMP コードポイントに必要な UCS-4 コードユニットは1 つだけです。len
unicode
以下は、サロゲート ペアが含まれているかどうかに関係なくすべての文字列を処理し、ナロー ビルドとワイド ビルドの両方で CPython 2.7 で動作することを確認しました。(ほぼ間違いなく、ワイド インタープリターのように文字列を指定することは、完全なサロゲート コードポイントを部分文字コード単位とは異なるものとしてu'\ud83d\udc4d'
表現したいという肯定的な欲求を反映しているため、自動的に修正されるエラーにはなりませんが、ここではそれを無視しています。これはエッジ ケースであり、通常は望ましいユース ケースではありません。)
以下@invoke
で使用するトリックは、モジュールの に何も追加せずに計算の繰り返しを回避する方法です__dict__
。
invoke = lambda f: f() # trick taken from AJAX frameworks
@invoke
def codepoint_count():
testlength = len(u'\U00010000') # pre-compute once
assert (testlength == 1) or (testlength == 2)
if testlength == 1:
def closure(data): # count function for "wide" interpreter
u'returns the number of Unicode code points in a unicode string'
return len(data.encode('UTF-16BE').decode('UTF-16BE'))
else:
def is_surrogate(c):
ordc = ord(c)
return (ordc >= 55296) and (ordc < 56320)
def closure(data): # count function for "narrow" interpreter
u'returns the number of Unicode code points in a unicode string'
return len(data) - len(filter(is_surrogate, data))
return closure
assert codepoint_count(u'hello \U0001f44d') == 7
assert codepoint_count(u'hello \ud83d\udc4d') == 7