Pythonに初挑戦(お題:不正なUTF-8バイト値を探す)

前のポスト で、なぜ Unicode の仕様を調べていたかというと、
実は Python でプログラムを書いてみたかっただけだったり。
 
お題:『UTF-8 文書に登場しないバイト値を探してみる』

我ながら、日々の業務に役立つ実用的なお題です。(違)
普段なら Perl か JavaScript で組みたくなってしまうけど、
今回は、Python に初挑戦してみたかった。
本屋さんで WEB+DB PRESS vol.46 も買って、特集3にあった
[速習]Python も読んでみました。
あとは、オフィシャルサイトの Python チュートリアル
Python リファレンスマニュアル を参照しました。
 
先に実行結果を書くと、以下の通り。(青字の部分は手書きコメント)
 
00..7F       1  U+000000..U+00007F   1バイトシーケンスで使用
80..BF   51102  U+000080..U+10FFFF   2バイト目以降で使用
C0..C1       0                       2バイトシーケンスのU+80未満は不正 ※1
C2..DF      64  U+000080..U+0007FF   2バイトシーケンスの1バイト目で使用
E0        2048  U+000800..U+000FFF   3バイトシーケンスの1バイト目で使用(U+800未満は不正) ※1
E1..EC    4096  U+001000..U+00CFFF   同上
ED        2048  U+00D000..U+00D7FF   同上(Surrogates Area以前) ※2
EE..EF    4096  U+00E000..U+00FFFF   同上(Surrogates Area以降) ※2
F0      196608  U+010000..U+03FFFF   4バイトシーケンスの1バイト目で使用(U+10000未満は不正) ※1
F1..F3  262144  U+040000..U+0FFFFF   同上
F4       65536  U+100000..U+10FFFF   同上(U+110000以降は不正) ※3
F5..FF       0                       5バイトシーケンス以上は不正 ※3

1列目が UTF-8 のバイトの値。
2列目がバイト値(の範囲)を利用する文字(コードポイント)の数。
3列目が実際に使っていた文字の範囲。

答え:『C0~C1、F5~FF は UTF-8 文書には登場しない』

これらのバイト値が入っていたら、その文書は正規な UTF-8 ではない。

※1 1文字を複数パターンのバイトシーケンスで表現できてしまう overlong form の問題。
※2 サロゲートペア 用なので BMP では使えない領域。
※3 第17面(U+110000~)以降は永久に使用されない。

実際に書いたコードは以下の通り。
初 Python なので、おかしな点がありそう。
(もっと Python らしい書き方があったら、教えてください)

#!/usr/bin/python
# -*- coding: utf-8 -*-


def utf8(u):
    if u < 0x00080:     # 7 bits
        return [ u ]
    elif u < 0x00800:   # 5+6 = 11 bits
        return [ 0xC0|(u>>6), 0x80|(u&0x3F) ]
    elif u < 0x10000:   # 4+6+6 = 16 bits
        return [ 0xE0|(u>>12), 0x80|((u>>6)&0x3F), 0x80|(u&0x3F) ]
    elif u < 0x110000:  # 3+6+6+6 = 21 bits
        return [ 0xF0|(u>>18), 0x80|((u>>12)&0x3F), 0x80|((u>>6)&0x3F), 0x80|(u&0x3F) ]
    else:
        raise "Invalid codepoint"

class CheckByte:
    def __init__(self):
        self.count = 0
        self.umin = -1
        self.umax = -1

class DispLine:
    def __init__(self,cnt,bmin,bmax,umin,umax):
        self.count = cnt
        self.bmin = bmin
        self.bmax = bmax
        self.umin = umin
        self.umax = umax

# 256個の配列を初期化する
clist = []
for b in range(256):
    f = CheckByte()
    clist.append(f)

# Unicodeのコードポイントを走査する
ulist = xrange(0,0xD800),xrange(0xE000,0x10000),xrange(0x10000,0x110000)
for blk in ulist:
    for u in blk:
        a = utf8(u)
        for b in a:
            clist[b].count += 1
            if clist[b].umin < 0:
                clist[b].umin = u
            if clist[b].umax < u:
                clist[b].umax = u

# 登場回数が同じバイトをサマる
dlist = []
last = -1
for b,c in enumerate(clist):
    if c.count != last:
        last = c.count
        h = DispLine( c.count, b, b, c.umin, c.umax )
        dlist.append( h )
    else:
        h = dlist[len(dlist)-1]
        h.bmax = b
        if h.umin > c.umin:
            h.umin = c.umin
        if h.umin < c.umin:
            h.umax = c.umax

# 順に表示する
for h in dlist:
    v = []
    v.append( '%02X' % h.bmin )
    if h.bmin != h.bmax:
        v.append( '-%02X' % h.bmax )
    else:
        v.append( '   ' )
    v.append( ' %7d' % h.count )
    if h.count:
        v.append( '  U+%06X - U+%06X' % (h.umin, h.umax) )
    print ''.join(v)
 
Debian の Python 2.5.2 で動作確認しています。

とりあえず動いたけど、自分で書いたコードで疑問に思ったこと。

・インスタンスの変数は全て __init__ 中で初期化するもの?
 (→初期化しなくても、外部から勝手に追加できるんだ…)
・中盤の CheckByte オブジェクトを256個初期化するループはイマイチ?
 256個の固定長の配列なので、1行でまるっと初期化できないのかな?
・順に表示するところで、リストを作って join するのは、定石か?

その他、Python で思ったこと。

・C→Perl→JavaScript とやってきた身からは、{} ブロックがないと
 読みにくいけど、慣れるものかな?
・末尾に『,』1文字を付けるだけでタプルになるのは、嬉しいのかな?
 (Perl でも最近の JavaScript でも配列作成は明示的)
・行頭インデントが tab か space かを区別するんですね…。
 (秀丸エディタでは辛いな…)
・変数を宣言する必要がないのに、未定義で参照するとエラーになるのは○。
 int とか my とか var とか付いていないと、なんだか不安になるけど。
 最初に変数を定義したブロックが、スコープになるのかな?(未確認)
・CheckByte・DispLine どちらも、Perl, JavaScript ならわざわざクラスを
 作らずに、適当なハッシュで済ましてしまいそう。
・Python には『a ? b : c』形式の三項演算子がない

ブログ気持玉

クリックして気持ちを伝えよう!

ログインしてクリックすれば、自分のブログへのリンクが付きます。

→ログインへ

なるほど(納得、参考になった、ヘー)
驚いた
面白い
ナイス
ガッツ(がんばれ!)
かわいい

気持玉数 : 1

ガッツ(がんばれ!)

この記事へのコメント

BLUEPIXY
2008年11月09日 19:19
>・Python には『a ? b : c』形式の三項演算子がない
『b if a else c』形式(2.5以降)ですね。

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