2006年11月23日 22:00 [Edit]

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

というわけで、404 Blog Not Found:There's more than one language to cook your problemsでPython & Ruby Cookbooksを一気読みしたので、気になる点を少しずつ書いて行くことにする。

まずは、文字の扱い。文字列でない点に注意。


少なくとも、文字列をバイト列と見なして相互変換することは、LLに限らずたいていの言語で出来るようになったのだけど、文字を文字として扱うという点に関しては各言語ともまちまちで、多言語派の私としては結構頭のいたいところ。

ここでは、私が一番流暢なPerlを軸に、RubyとPythonではどうなっているのかを調べてみた。

数値から文字へ(chr)

まずはPerlの例。ここでは簡便のため、以降のscriptはすべて

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
binmode STDOUT, ':utf8';
sub say { print @_, "\n" };

が頭についているものとする。

say chr(0x61);
say chr(0x5F3E);
say chr(0x2A6B2);

を実行すると、以下のようにUTF-8端末(例えばMac OS X の Terminal.app のデフォルト)に表示されるはずだ。

U+2A6B2
a
弾
𪚲

最後の文字は、Unicode表記だとU+2A6B2、月と亀をあわせた形をしている。画像だと右のような感じだ。

Pythonでは

これをPythonでやろうとすると、こうなってしまう。

print chr(0x61)
print chr(0x5F3E)
print chr(0x2A6B2)
a
Traceback (most recent call last):
  File "unichar.py", line 4, in ?
    print chr(0x5F3E)
ValueError: chr() arg not in range(256)

一行目のみうまく行くのだが、chr()はUnicode未対応なのだ。その代わりPythonにはunichr()という関数が別に用意されている。しかし、それだけだと、こうなってしまう。

Traceback (most recent call last):
  File "unichar.py", line 4, in ?
    print unichr(0x5F3E)
UnicodeEncodeError: 'ascii' codec can't encode character u'\u5f3e' in position 0: ordinal not in range(128)

Unicode文字をそのまま出力できないという点は、実はperlも似ていて、そのためにbinmode STDOUT, ':utf8';という行がある。pythonでは、以下のようにすればいい。

print unichr(0x5F3E).encode('utf-8')

これで「弾」までは表示できる。ちなみにこれはperlだとencode('utf-8', chr(0x5F3E))に相当する。

しかし0x2A6B2はうまく行かない。

Traceback (most recent call last):
  File "unichar.py", line 5, in ?
    print unichr(0x2A6B2).encode('utf-8')
ValueError: unichr() arg not in range(0x10000) (narrow Python build)

と文句を言われてしまう。なんとかならないだろうか。

以下が、ヒントになるかも知れない。

2.4.1 文字列リテラル
\Uxxxxxxxx 32-bit の 16 進数値 xxxxxxxx を持つ文字 (Unicode のみ) (2)

こんなんでどうだろうか。

def utf32chr(n):
    return eval("u\'\\U%08X\'" % n)

print utf32chr(0x2A6B2).encode('utf-8');

うまく行ったようだ。が、何ともめんどい。

rubyでは

意外なことに、この分野で一番進んでいないのがRubyかも知れない。

#!/usr/bin/ruby
$KCODE = 'u'
require 'jcode'

p 0x61.chr
p 0x5f3e.chr
p 0x2A6B2.chr
"a"
unichar.rb:6:in `chr': 24382 out of char range (RangeError)
        from unichar.rb:6

むべもない。Fixnum#utf8chrとかを定義すればいいのだろうか。

class Fixnum
    def utf8chr
        if self < 0x80
            chars = [ self ]
        elsif self < 0x800
            chars = [
                (self >> 6)                | 0b1100_0000,
                (self       & 0b0011_1111) | 0b1000_0000
            ]
        elsif self < 0x10000
            chars = [  
               (self >> 12)                | 0b1110_0000,
              ((self >>  6) & 0b0011_1111) | 0b1000_0000, 
               (self        & 0b0011_1111) | 0b1000_0000
            ]
        elsif self < 0x110000
            chars = [  
               (self >> 18)                | 0b1111_0000,
              ((self >> 12) & 0b0011_1111) | 0b1000_0000, 
              ((self >>  6) & 0b0011_1111) | 0b1000_0000, 
               (self        & 0b0011_1111) | 0b1000_0000
            ]
        else
            raise RangeError, "#{self} is out of utf8 range";
        end
        chars.map{|c| c.chr}.join('')
    end
end

p 0x61.utf8chr
p 0x3b1.utf8chr
p 0x5f3e.utf8chr
p 0x2A6B2.utf8chr

うまく行ったみたい。

"a"
"α"
"弾"
"𪚲"

でも、これくらい標準ライブラリーに入っていてもよさげでは?

追記:

class Fixnum
    def utf8chr
        [self].pack('U');
    end
end

でOKですね。成瀬さんありがとう。

文字から数値へ(ord)

もやろうかと思ったけど、とりあえずchrが長くなったのでordは別entryということで。

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


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

この記事へのトラックバック
正しくは"UTF-8で表現できる"が接頭辞につきます。また、前提として僕はエンコ...
Rubyで全ての漢字を列挙する【polog】at 2007年12月01日 01:30
というわけで、ord篇はこちら。 404 Blog Not Found:perl, python & ruby - chr() vs. Unicode とりあえずchrが長くなったのでordは別entryということで。
perl, python & ruby - ord() vs. Unicode【404 Blog Not Found】at 2006年11月26日 02:55
この記事へのコメント
なにがcharacterなのかの定義が難しくありませんか?
コードポイントを表現するだけではユニコード対応とは言えないと思いますよ。
Posted by PuniPuni at 2007年03月18日 03:28
H.E.L.P子飼さんw
誰でもいいってわけじゃないんだほんとにw
http://d.hatena.ne.jp/ryosuke321/
Posted by 大勲位鈴木亮佑 at 2006年11月24日 08:34
oops,
def Fixnum
def utf8chr
[self].pack('U')
end
end

ですね。失礼。
Dan the Occasional Rubyist
Posted by at 2006年11月24日 01:03
成瀬さん、
あ、perlと同じなのですね、formatが。
これでFixnum#utf8chrの最後の
chars.map{|c| c.chr}.join('')

chars.unpack('U')
になりますね。
ありがとうございます。
Dan the Occasional Rubyist
Posted by at 2006年11月24日 00:58
Rubyには[0x61].pack('U')ってのがありますよん
Posted by 成瀬 at 2006年11月24日 00:31