2006年11月26日 02:45 [Edit]

perl, python & ruby - ord() vs. Unicode

というわけで、ord篇はこちら。

404 Blog Not Found:perl, python & ruby - chr() vs. Unicode
とりあえずchrが長くなったのでordは別entryということで。

文字から数値へ(ord)

まずはPerlの例。

#!/usr/local/bin/perl
use strict;
use warnings;
use utf8;
binmode STDOUT, ':utf8';
sub say { print @_, "\n" };
say ord "\x{61}";
say ord "\x{3b1}";
say ord "\x{5F3E}";
say ord "\x{2A6B2}";
97
945
24382
173746

Pythonでは

まずはその前に前回の予習。

pythonグループ - faerie の日記の Python - 0x10000 以上の Unicode 文字を扱うには
--enable-unicode=ucs4 付きでビルドすればいいらしい。

これはPythonとは思えない美しくない仕様。これではBytecodeもScriptもPortableではなくなってしまうではないか!

少なくともMac OS Xの/usr/bin/python (v2.3.5)は、--enable-unicode=ucs4 ではなかった。

ついでに、eval("u\'\\U%08X\'" % n) より (r'\U%08X' % n).decode("unicode_escape") のほうが良い。

こちらはなるほど。

以上を踏まえて、以下はデフォルトの仕様、すなわちunicode objectの内部表現はUTF-16だという前提。まずは以下をそのまま実行してみる。

print ord(u'\U00000061')
print ord(u'\U000003b1')
print ord(u'\U00005F3E')
print ord(u'\U0002A6B2')

案の定、こうなる。

97
945
24382
Traceback (most recent call last):
  File "uniord.py", line 10, in ?
    print ord(u'\U0002A6B2')
TypeError: ord() expected a character, but string of length 2 found

とりあえず、以下のようにすれば動くようにはなる。

def uniord(c):
    if len(c) == 1:
        return  ord(c)
    else:
        return 0x10000 + (ord(c[0]) - 0xD800) * 0x400 + (ord(c[1]) - 0xDC00)

print uniord(u'\U00000061')
print uniord(u'\U000003b1')
print uniord(u'\U00005F3E')
print uniord(u'\U0002A6B2')

とはいえ、これはあくまで一文字だけの例。実際には、文字列から本来の文字を一つ一つ取り出さなくてはならない。Perlのsplit //, $strないしunpack 'U*', $str相当のことはどうやったらよいだろう。とりあえず、泥臭い回答。

def uniunpack(ustr):
    result = []
    i = 0
    while i < len(ustr) :
        o = ord(ustr[i])
        if 0xD800 <= o and o < 0xDC00:
            i += 1
            o -= 0xD800
            o *= 0x400
            o += 0x10000 + (ord(ustr[i])  - 0xDC00)
        result.append(o)
        i += 1
    return result

str =  u'\u0061\u03b1\u5F3E\U0002A6B2'
print str.encode('utf-8')
print map(ord, str)
print uniunpack(str)
aα弾𪚲
[97, 945, 24382, 55401, 57010]
[97, 945, 24382, 173746]

pythonはpushでなくてappendなのね:)

rubyでは

ありがたいことに、String.unpack('U*')でいける。成瀬さんありがとう!

$KCODE = 'u'
require 'jcode'
str =  [0x61, 0x3b1, 0x5F3E, 0x2A6B2].pack('U*')
p str
str.unpack('U*').each{ |ord| p ord }
"aα弾𪚲"
97
945
24382
173746

dotの連鎖とblockの美しさはいささかも損なわれていない。

ただし、欠点もある。文字列リテラルでPerlの\x{2A6B2}、Pythonの\u03b1ないし\U0002A6B2という表記法が用意されていないのだ。このあたり、Rubyistのみなさんはどうなさっているのだろうか?str = 'aα弾' + [0x2A6B2].pack('U')とか、それともstr = "aα弾#{ [0x2A6B2].pack('U') }"とか。

やはりなんだかんだ言って、rubyはperlishに優しいが、Unicodeにはちょっと冷たい。

Dan the Man with Too Many Characters to Juggle in Too Many Languages


この記事へのトラックバックURL

この記事へのトラックバック
技術評論社書籍編集部池本さんより献本御礼。 入門正規表現 岩谷宏 初出2008.03.05;販売開始まで掲載 404 Blog Not Found:書評x3 - SQL本三大対決言語内言語としてこれだけ使われるようになったSQL。そうするともう一つの言語内言語、正規表現が気になる。しかし...
get($one) if $you =~ /regexp user/;# - 書評 - 入門正規表現【404 Blog Not Found】at 2008年03月05日 22:58
この記事へのコメント
RubyはCESなので、"\x{3042}"と書いたからといって、
3042がU+3042のこととみなすのが難しいというのもあるのかもしれません。
Posted by 成瀬 at 2006年11月26日 21:37