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 のデフォルト)に表示されるはずだ。
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
あ、perlと同じなのですね、formatが。
これでFixnum#utf8chrの最後の
chars.map{|c| c.chr}.join('')
が
chars.unpack('U')
になりますね。
ありがとうございます。
Dan the Occasional Rubyist
def Fixnum
def utf8chr
[self].pack('U')
end
end
ですね。失礼。
Dan the Occasional Rubyist
誰でもいいってわけじゃないんだほんとにw
http://d.hatena.ne.jp/ryosuke321/
コードポイントを表現するだけではユニコード対応とは言えないと思いますよ。