Unicode HOWTO

by A.M.Kuchling

Version 1.02

このHOWTOは、PythonのUnicodeサポートと私たちがUnicodeを使って何かしようとするときに起こるさまざまな問題について論じています。

Unicode入門

文字コードの歴史

1968年、ASCIIという略号でよく知られる情報交換用米国標準コード(American Standard Code for Information Interchange)が標準化されました。ASCIIではさまざまな文字に対応する0から127の数字のコードを定義しました。例えば、小文字の'a'は97という値を持つというような具合です。

ASCIIは米国で開発された標準だったので、アクセント記号のない文字しか定義していませんでした。'e'はありましたが'é' や'Í'はありません。これは、アクセント記号を必要とする言語はASCIIでは正確に表現できないことを意味します。(実際にはアクセント記号がないという欠陥は英語でも問題です。'naïve'や'café'は英単語ですし、'coöperate'と綴るスタイルを使う出版社もあります。)

しばらくの間、人々が書いていたのは単にアクセントを表示しないコンピュータプログラムでした。私が思い出すのは1980年代中ごろの仏語の雑誌で配布されたApple][BASICのプログラムです。その中にはこんな行がありました:

PRINT "FICHER EST COMPLETE."
PRINT "CARACTERE NON ACCEPTE."

これらのメッセージはアクセントを含むはずなので、フランス語が読める人からは間違ってみえるでしょう。

1980年代、ほとんど全てのパソコンは8ビットでした、つまり1バイトごとに0から255の整数値を持つことができたということです。ASCIIコードは127までしかありませんので、128から255までの値をアクセント付き文字に割り当てていたパソコンが何機種かありました。違う機種では別々のコードを使っていました。その一方、ファイルのやりとりに支障が起きました。最終的には128から255の数字とアクセント付き文字の対応セットは様々なものが一般的に使われるようになりました。対応セットにはISOで定義された本物の標準と、一社か数社により作られてなんとか定着したデファクト標準がありました。

255個の文字はさほど多いとはいえません。たとえば、西ヨーロッパ圏で使われるアクセント付き文字とロシア語のアルファベットは128から255の間に収まりません、127個より多くの文字があるからです。

それぞれのファイルに異なるコードで書き込むこともできます(ロシア語ファイルはKOI8で、フランス語ファイルはLatin1で)。しかし、フランス語の文章を書いているときに、もしロシア語のテキストを引用したくなったら?1980年代、人々はこの問題を解決したいと思うようになり、Unicode標準化への努力が始まったのです。

Unicodeは1文字に8ビットではなく16ビットを使うところから始まりました。16ビットとは2^16 = 65,536個の独立した値をとれることで、多数の異なる種類の文字集合からの多数の異なる文字を表現することが可能になりました;Unicodeの初期の目標の一つは全ての言語の文字集合を含むことでした。16ビットでも目標に届かないことがわかって、現在のUnicode仕様ではもっと広い領域を使います。0から1,114,111 (16進数で0x10ffff)です。

関係するISO標準に、ISO 10646があります。UnicodeとISO 10646はもともと別の取り組みでしたが、Unicode 1.1から両者は統合されました。

(上のUnicodeについての歴史はかなり単純化したものです。平均的なPythonプログラマが歴史的な細部にこだわる必要はないと私は思います。;もっと知りたければReferenceからリンクを張ったUnicode consortiumのサイトを見てください。)

定義

文字とは文章を構成する最小単位です。'A'、'B'、'C'などは全て違う文字です。'È' や'Í'についても同じです。文字は抽象概念であり、言語や文脈によって変化します。たとえばオーム(Ω)記号はギリシャ語の大文字のオメガ(Ω)と同じように通常描かれます(同じフォントでは全く同じに見える可能性もあります)、しかしこれらは異なる意味を持つ別の文字なのです。

Unicode標準仕様には文字がコードポイントによってどのように表されるかについて記載があります。コードポイントは整数値で通常16進数にて表示されます。U+12caと表記すると、0x12ca(10進法で4810)を表します。Unicode標準仕様には文字と対応するコードポイントがリストされた多数の表が含まれています。

0061    'a'; LATIN SMALL LETTER A
0062    'b'; LATIN SMALL LETTER B
0063    'c'; LATIN SMALL LETTER C
...
007B    '{'; LEFT CURLY BRACKET

厳密には、これらの定義は'これは文字U+12caである'ということが無意味であることを示唆しています。U+12caはコードポイントであり、コードポイントは特定の文字を示します;このケースでは、'ETHIOPIC SYLLABLE WI'を表しています。正式ではない文脈ならば、コードポイントと文字との区別は意識されないことがあります。

文字はスクリーン上や紙の上で図画要素の集合として表されます。これをグリフと呼びます。大文字のAに対応するグリフは、たとえば2本の斜めの線と1本の水平線からなりますが、正確な細部については使っているフォントに依存します。ほとんどのPythonコードはグリフについて心配する必要はありません。正しいグリフを出力するのは一般的にGUIツールキットか端末フォントレンダラの仕事だからです。

エンコーディング

前章を要約:Unicode文字列はコードポイントの配列です。コードポイントは0から0x10ffffまでの整数値をとります。この配列はメモリ上では1組のバイト(つまり、0から255までの値)の固まりである必要があります。Unicode文字列をバイト列に変換する規則をエンコーディングと呼びます。

最初にあなたが思いつくかもしれないエンコーディングは32ビットの数字の列でしょう。この表現では、"Python"はこんな風に見えます:

   P           y           t           h           o           n
0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00 
   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 

この表現は単純ですが、使用の際にはいくらかの問題があります。

  1. 可搬性がない;異なるプロセッサではバイト・オーダーが違う。
  2. 無駄が多い。ほとんどの文章ではコードポイントは127より小さいか、255より小さい、よってヌルバイトで占められるスペースがたくさんできてしまう。上の文字列は24バイトだが、ASCIIでは6バイトしか必要でない。RAM消費量の増加はあまり問題にならない(デスクトップコンピュータは何メガバイトものメモリを搭載している。テキストデータがそこまで大きくなることはあまりない)が、使用するディスク容量とネットワーク帯域が4倍になることは許容しがたい。
  3. strlen()などの既存のC関数と互換性がなく、ワイド文字列を扱う新たな関数群が必要になる。
  4. テキストデータに関して多くのインターネット標準の定義では、ヌルバイトを埋め込んだ文字列を扱えない。

一般的にこのエンコーディングは使われず、より効率的で使いやすい他のエンコーディングのほうが好まれます。

エンコーディングは全てのUnicode文字を扱う必要はありませんし、また扱っていないものがほとんどです。たとえば、Pythonのデフォルトのエンコーディングは'ascii'です。Unicode文字列をASCIIエンコーディングに変換するルールは簡単です。コードポイントごとに:

  1. もしコードポイントが128より小さかったら、それぞれの値はコードポイントの値と等しい。
  2. もしコードポイントが128より大きかったら、Unicode文字列はこのエンコーディングでは表示できない。(この場合、PythonはUnicodeEncodeError例外をraiseします。)

ISO-8859-1としても知られるLatin-1も似たようなエンコーディングです。Unicodeのコードポイント0-255はLatin-1の同じ値と対応しますので、このエンコーディングに変換するにはコードポイントをバイト値とするだけでよいのです;もし255より大きいコードポイントに遭遇すれば、その文字列はLatin-1にはエンコードできません。

エンコーディングは必ずしもLatin-1のような1対1のマッピングである必要はありません。IBMのEBCDICを例に考えましょう、このエンコーディングはIBMのメインフレームで使われていました。全てのアルファベットの値は1つ区画にはなく、'a'から'i'の値は129から137になりますが、'j'から'r'の値は145から153となっていました。もしEBCDICをエンコーディングの一つとして使用したいと思ったら、変換のために参照テーブルのようなものを使ったでしょう。しかしこれはほとんど内部的なディテールにすぎません。

UTF-8は最も広く使われるエンコーディングの一つです。UTFは"Unicode Transformation Format"の略で、8は8ビット数字がエンコーディングに使われることを表します(UTF-16も存在しますが、UTF-8ほどは使われてません)。UTF-8は次にあげるルールに従います:

  1. もしコードポイントが128より小さければ、対応するバイト値で表現される。
  2. もしコードポイントが128と0x7ffの間にあれば、それぞれ128から255の値をとる2バイト列 に変換される。
  3. 0x7ffより大きいコードポイントは3、4バイトの列に変換される。そこではそれぞれのバイト値は128から255の間にある。

UTF-8はいくつかの便利な性質をそなえます:

  1. 全てのUnicodeコードポイントを扱うことができる。
  2. Unicode文字列はヌルバイトを含まない文字列に変換される。これによりバイト・オーダー問題が避けられる、つまりUTF-8文字列はC関数のstrcpy()などが使え、ヌルバイトを扱えないプロトコルで送信できる。
  3. ASCIIテキストは正しいUTF-8テキストにもなる。
  4. 正しくコンパクトな設計である;ほとんどのコードポイントは2バイトにおさまり、128より小さい値なら1バイトにおさまる。
  5. もし欠損や間違いがあっても、次のUTF-8でエンコードされたコードポイントがどこにあるかが発見できるので、エラーから回復可能である。でたらめな8ビットデータが間違って正しいUTF-8に見えてしまうことも起こりにくい。

リファレンス

Unicode協会のサイト<http://www.unicode.org>には文字の図表や用語集、pdf版の仕様書などが置いてあります。少々の読みにくさは覚悟してください。 <http://www.unicode.org/history/>はUnicodeの誕生と発展にまつわる年代記です。

標準仕様を理解する助けになるのが、Jukka Korpelaの書いたUnicodeの文字表の読み方ガイドです。<http://www.cs.tut.fi/~jkorpela/unicode/guide.html>で読めます。

次に、Roman CzyborraはUnicodeの基本原則について解説をしています。これは<http://czyborra.com/unicode/characters.html>にあります。Czyborraは他にいくつものUnicode関連文章を書いています、<http://www.cyzborra.com>にて入手してください。

他にもよい入門記事が二つあります。Joel Spolskyの <http://www.joelonsoftware.com/articles/Unicode.html>とJason Orendorffの<http://www.jorendorff.com/articles/unicode/>です。もしこのHOWTOがぴんとこなかったら、先へ進む前にこれらのうちどれか1つの記事は読んでみるべきです。

Wikipediaの記事もしばしば役に立ちます; たとえば"character encoding" <http://en.wikipedia.org/wiki/Character_encoding>やUTF-8 <http://en.wikipedia.org/wiki/UTF-8>の記事を読んでみるとよいでしょう。

PythonのUnicodeサポート

さあ、既にUnicodeの基礎概念については学習したので、今度はPythonのUnicode機能を見ていきます。

Unicode型

Unicode文字列は、built-in型の一つであるunicode型のインスタンスとして表されます、この型はstr型の上位型でもあるbasestring型を由来にもちます;よってある値が文字列型かどうかはisinstance(value, basestring)で判別できます。Python内部では、Unicode文字列は16か32ビットの整数値で表されます。Pythonのコンパイル設定によって、どちらになるかは決まります。

unicode() コンストラクタはunicode(string[, encoding, errors])というシグネチャを持ちます。 全ての引数は8ビット文字列である必要があります。指定したエンコーディングにより、1つめの引数をUnicodeへと変換します;もしencoding引数がない場合にはASCIIエンコーディングが変換に使われるので、127より大きな値をもつ文字はエラーになるでしょう。

>>> unicode('abcdef')
u'abcdef'
>>> s = unicode('abcdef')
>>> type(s)
<type 'unicode'>
>>> unicode('abcdef' + chr(255))
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 6: 
                    ordinal not in range(128)

errors 引数は入力をエンコーディング規則にのっとって変換できない時の挙動を指定します。この変数に対する正しい値は'strict' (UnicodeDecodeError exceptionをraiseします)、'replace' ('REPLACEMENT CHARACTER'であるU+FFFDで置換します)、'ignore' (その文字を結果のUnicode文字列に含めません)です。下の例で違いがわかります。:

>>> unicode('\x80abc', errors='strict')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0x80 in position 0: 
                    ordinal not in range(128)
>>> unicode('\x80abc', errors='replace')
u'\ufffdabc'
>>> unicode('\x80abc', errors='ignore')
u'abc'

エンコーディングは、エンコーディング名を含んだ文字列により指定されます。Python 2.4はおおよそ100種類ものエンコーディングを含んでいます;一覧表はPythonライブラリ・リファレンスの <http://docs.python.org/lib/standard-encodings.html> を見てください。いくつかのエンコーディングは複数の名前を持ちます;たとえば、'latin-1'、'iso_8859_1'、'8859'は全て同じエンコーディングを表します。

1文字からなるUnicode文字列はbuilt-in関数の unichr()で作ることができます。この関数は整数値を引数にとり、対応するコードポイントを含む長さ1のUnicode文字列を返します。逆の操作を行うのはbuilt-in関数のord()で、これは1文字のUnicode文字列を引数にとり、コードポイントの値を返します:

>>> unichr(40960)
u'\ua000'
>>> ord(u'\ua000')
40960

unicodeインスタンスには、8ビット文字列のメソッドと同じメソッドが多数あり、検索や整形などを行います:

>>> s = u'Was ever feather so lightly blown to and fro as this multitude?'
>>> s.count('e')
5
>>> s.find('feather')
9
>>> s.find('bird')
-1
>>> s.replace('feather', 'sand')
u'Was ever sand so lightly blown to and fro as this multitude?'
>>> s.upper()
u'WAS EVER FEATHER SO LIGHTLY BLOWN TO AND FRO AS THIS MULTITUDE?'

これらのメソッドの引数はUnicode文字列と8ビット文字列のどちらでもありうることを覚えていてください。8ビット引数は実行前にUnicodeへと変換されます;PythonデフォルトのASCIIエンコーディングが使われることになるでしょう、これより127以上の値をもつ文字は例外を起こします:

>>> s.find('Was\x9f')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0x9f in position 3: ordinal not in range(128)
>>> s.find(u'Was\x9f')
-1

上記のことから、文字列を扱う大部分のPythonコードはUnicode文字列に対してもちゃんと動くことでしょう。(Unicodeの入出力のためのコードには更なる手直しが必要です。これについては後で書きます。)

もう一つの重要なメソッドは.encode([encoding], [errors='strict']), です。このメソッドはUnicode文字列の8ビット文字列バージョンを、指定されたエンコーディングで返します。errors 変数はunicode() コンストラクタのものとほぼ同じで、一つ選択肢が増えただけです、'strict'、'ignore'、'replace'のほかに'xmlcharrefreplace'を選ぶことができます。'xmlcharrefreplace'はXMLのcharacter referenceを参照して置換します。下の例は異なる結果を導きます:

>>> u = unichr(40960) + u'abcd' + unichr(1972)
>>> u.encode('utf-8')
'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
'abcd'
>>> u.encode('ascii', 'replace')
'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
'&#40960;abcd&#1972;'

Pythonの8ビット文字列は.decode([encoding], [errors])メソッドを持ちます。このメソッドは与えられたエンコーディングにより文字列を解釈します:

>>> u = unichr(40960) + u'abcd' + unichr(1972)   # Assemble a string
>>> utf8_version = u.encode('utf-8')             # Encode as UTF-8
>>> type(utf8_version), utf8_version
(<type 'str'>, '\xea\x80\x80abcd\xde\xb4')
>>> u2 = utf8_version.decode('utf-8')            # Decode using UTF-8
>>> u == u2                                      # The two strings match
True

エンコーディングを登録したりアクセスするための低レベルなルーチンはcodecsモジュールに入っています。しかしながら、このモジュールに含まれるエンコーディング・デコーディング関数はより低レベルな実装となっているため、日常的に使いやすいものではありません。よって、わたしはここでcodecsモジュールについて解説しません。もしあなたが完全に新しいエンコーディングを実装しようとするのであれば、codecsモジュールについて勉強しなくてはならないでしょう。しかしエンコーディングの実装は特殊な仕事であるため、このHOWTOではカバーしません。これについてはPythonのドキュメンテーションで調べてみてください。

codecsモジュールの中で最も頻繁に使われるのは codecs.open() 関数です。この関数については入出力の章で扱います。

Pythonソースコード内でのUnicodeリテラル

Pythonソースコード内では、Unicodeリテラルはプリフィクスの'u'か'U'をつけた文字列として書きます:u'abcdefghijk'。特定のコードポイントはエスケープシークエンスの\uをつけて書くことができます。エスケープシークエンスに続く4桁の16進数でコードポイントを表します。エスケープシークエンス\Uも同じようなもので、8桁でなく4桁の16進数を使うところが違います。

Unicodeリテラルは8ビット文字列と同じエスケープシークエンスを使うこともできます。\xもその一つですが、2桁の16進数までしか取れないので任意のコードポイントを表すことはできません。8進数エスケープはU+01ffまで、つまり8進数で777まで表すことができます。

>>> s = u"a\xac\u1234\u20ac\U00008000"
           ^^^^ two-digit hex escape
               ^^^^^^ four-digit Unicode escape 
                           ^^^^^^^^^^ eight-digit Unicode escape
>>> for c in s:  print ord(c),
... 
97 172 4660 8364 32768

127より大きいコードポイントに対してエスケープシークエンスを使うのは、それが少量ならよい方法です。しかし、もしフランス語か他のアクセントつき文字を使う言語でメッセージを表示させるプログラムを書いていて、たくさんのアクセントつき文字を使いたいのなら、これは憂鬱な作業になります。built-in関数の unichr()を使う方法もありますが、これは一層退屈な仕事です。

理想的には、あなたはリテラルを自分の言語のエンコーディングで書きたいと思うでしょう。そうすれば、あなたはお気に入りのエディタでPythonのソースコードを編集でき、そこにはアクセントつき文字を表示させて、実行時にも正しい文字を出力できます。

Pythonはどんなエンコーディングで書いたUnicodeリテラルでもサポートしていますが、あなたは使うエンコーディングを宣言する必要があります。宣言は特殊なコメント行をソースコードの1行目か2行目に含めることによって行われます:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

u = u'abcdé'
print ord(u[-1])

このシンタックスは、Emacsでファイルにローカルな変数を指定する表記法にインスパイヤされています。Emacsはたくさんの異なる変数についてこの表記法をサポートしていますが、Pythonで使えるのは'coding'だけです。-*-記号はコメントが特別なものであることを指しています。この中にcodingとあなたが選んだエンコーディングの名前を':'で区切って入れなければいけません。

エンコーディング指定のコメント行を含めなければ、デフォルトのエンコーディングはASCIIになるでしょう。2.4以前のPythonは「欧州寄り」だったので、文字列リテラルに関しては、Latin-1をデフォルトエンコーディングとしていました;Python 2.4では、127より大きい文字も使用可能ですが警告が出るようになっています。たとえば、次のプログラムにはエンコーディングの宣言はありません:

#!/usr/bin/env python
u = u'abcdé'
print ord(u[-1])

これをPython 2.4で実行すると、下のような警告が出るでしょう。

amk:~$ python p263.py
sys:1: DeprecationWarning: Non-ASCII character '\xe9' 
     in file p263.py on line 2, but no encoding declared; 
     see http://www.python.org/peps/pep-0263.html for details

Unicodeの属性

Unicode仕様書にはコードポイントに関する情報のデータベースが入っています。定義された各々のコードポイントに対応する情報は文字の名前、カテゴリ、もしあれば数字の値を含みます。(Unicodeにはローマ数字や1/3、1/4などの分数を表す文字が存在します。)双方向文字やその他の表示に関係するプロパティもあります。

次のプログラムはいくつかのキャラクタの文字についての情報を表示させ、そのうち一つの数字の値を画面出力するものです:

import unicodedata

u = unichr(233) + unichr(0x0bf2) + unichr(3972) + unichr(6000) + unichr(13231)

for i, c in enumerate(u):
    print i, '%04x' % ord(c), unicodedata.category(c),
    print unicodedata.name(c)

# Get numeric value of second character
print unicodedata.numeric(u[1])

実行すると出力は以下のようになります:

0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE
1 0bf2 No TAMIL NUMBER ONE THOUSAND
2 0f84 Mn TIBETAN MARK HALANTA
3 1770 Lo TAGBANWA LETTER SA
4 33af So SQUARE RAD OVER S SQUARED
1000.0

カテゴリ・コードはその文字の性質を表す略字です。これらは、"Letter(文字)"、"Number(数字)"、"Punctuation(句読点)"、"Symbol(記号)"といったカテゴリに分類分けされ、さらにサブカテゴリに分けられます。上の出力を例にとると、'Ll'は'Letter, lowercase(文字、小文字)'、'No'は"Number, other(数、その他)"、'Mn'は"Mark, nonspacing(マーク、非空白)"、'So'は"Symbol, other(記号、その他)"となります。カテゴリ・コードのリストが欲しければ、 <http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values> を見てください。

リファレンス

PythonライブラリリファレンスのUnicode文字列型と8ビット文字列型について<http://docs.python.org/lib/typesseq.html>.

unicodedataモジュールについて <http://docs.python.org/lib/module-unicodedata.html>.

codecsモジュールについて <http://docs.python.org/lib/module-codecs.html>.

Marc-André LemburgがEuroPython 2002で行った発表"Python and Unicode"。PDF版のスライドが<http://www.egenix.com/files/python/Unicode-EPC2002-Talk.pdf>で入手可能、PythonのUnicode機能の設計についてのすばらしい概説です。

Unicodeデータを読み書きする

Unicodeデータを扱うコードをいくらか書いたなら、次の問題は入出力です。どうやってUnicode文字列をプログラムに組込むのでしょう?また、どうやってUnicodeを保存や転送に適した形にするのでしょうか?

入力元と出力先に依存した操作を何もしなくてもいいことにするのも可能です;その場合あなたは自分のアプリケーションが使うライブラリがUnicodeをネイティブサポートしているかどうかを確認しなくてはいけません。例えばXMLパーサはしばしばUnicodeデータを返します。多くのリレーショナルデータベースもUnicode値のカラムをサポートし、SQLクエリに対しUnicode値を返すこともできます。

通常はUnicodeデータを特定のエンコーディングに変換してから、ディスクに書きんだりソケットを通じて送信したりします。それらの仕事を全部自分でやることもできます:ファイルを開く、8ビット文字列をそこから読む、文字列をunicode(str, encoding)関数を使って変換する。しかしながら、手動でそれをやる方法はおすすめできません。

問題の1つはマルチバイトのエンコーディングです;1つのUnicode文字は数バイトで表現されることがあります。もしあなたがファイルを適当なサイズの塊で読みたいとすれば(例えば、1Kか4K)、1文字のUnicodeをエンコードするバイト列の一部だけを最後に読み取ってしまった場合を考えて、あなたはエラー処理用のコードを書く必要があります。 1つの解決策は、ファイル全体をメモリに読みこんでしまうことです。しかし、これでは巨大なファイルを使うことの妨げになります;もし2GBのファイルを読む必要が出たら、2GBのRAMが必要になります。(本当はもっと多くのメモリが要ります、一時的にせよエンコードされた文字列とUnicode文字列との両方をメモリ内に記憶する必要があるためです。)

それを解決する方法は低レベルのデコード・インターフェイスを使って、コード・シークエンスの状態を捕捉することでしょう。これを実装したものが既に用意されています:codecsモジュールは、open()関数の亜種を含みます。この関数が返すオブジェクトは普通のファイルのようにふるまいますが、ファイルの中身は特定のエンコーディングであることを仮定していて.read().write()関数でUnicodeパラメータを使うことができます。

関数のパラメータは次のようになります:open(filename, mode='rb', encoding=None, errors='strict', buffering=1)。modeはbuilt-inのmode関数とちょうど対応して'r''w''a'、をとることができます; '+'を足すことで更新モードになります。bufferingは、普通の関数で使われるのと同等の変数です。encodingは使用するエンコーディングを与える変数です;もしNoneを指定しすると、8ビット文字列をサポートする通常のPythonファイルオブジェクトが返されます。そうでない場合は、wrapperオブジェクトが返され、読み書きするデータはwrapperにより必要なときに変換されます。errorsはエンコーディングにエラーが生じたときの挙動を指定し、'strict'、'ignore'、'replace'から選ぶことができます。

この関数のおかげで、ファイルからUnicodeを読みこむコードが簡単になります:

import codecs
f = codecs.open('unicode.rst', encoding='utf-8')
for line in f:
    print repr(line)

更新モードでファイルを開くこともできます。この場合、読み書き両方が可能です:

f = codecs.open('test', encoding='utf-8', mode='w+')
f.write(u'\u4500 blah blah blah\n')
f.seek(0)
print repr(f.readline()[:1])
f.close()

Unicode文字のU+FEFFはバイト・オーダー・マーク(BOM)として使われています。この文字はしばしばファイルの最初につけられ、ファイルのバイト・オーダーを自動認識するための補助になります。UTF-16のようなエンコーディングはBOMがファイルの先端につくことを要求します。UTF-16などが使われるときは、BOMが自動的に最初の文字として書き込まれ、ファイル読み込みの際には暗黙のうちに飛ばされます。UTF-16などのエンコーディングには、リトル・エンディンアンのための'utf-16-le'、ビッグ・エンディアンのための'utf-16-be'といったように、変異体が存在します。これらではバイト・オーダーを明確に指定するかわりに、BOMはスキップしません。

Unicodeファイル名

現在、ほとんどのオペレーションシステムがUnicode文字を含んだファイル名をサポートしています。たいてい、この機能はUnicode文字列をシステムに依存した適当なエンコーディングに変換することで実装されています。例を挙げるとMacOS XはUTF-8を使用しているほか、Windowsではエンコーディングは設定により変更可能になっています;Windows上ではPythonは"mbcs"という名前を使って、現在設定している任意のエンコーディングを参照することができます。Unixシステムでは、LANGLC_CTYPE環境変数を設定したときのみファイルシステムエンコーディングが存在します。もし設定しなければデフォルトエンコーディングはASCIIです。

The sys.getfilesystemencoding()関数は使用しているシステムのエンコーディングを返します。もしあなたが手動でエンコーディングを設定したいなら使ってもよいですが、わざわざそうする理由もないでしょう。読み書きのためにファイルを開くとき、通常ファイル名にUnicode文字列を与えるだけで、ファイル名は自動的に正しいエンコーディングに変換されます。:

filename = u'filename\u4500abc'
f = open(filename, 'w')
f.write('blah\n')
f.close()

osモジュールのos.stat()関数も、Unicodeファイル名をサポートしています。

os.listdir()はファイル名を返しますが、ここで問題が生じます: Unicodeのファイル名を返せばいいのでしょうか、それともエンコードされた8ビット文字列を返せばよいのでしょうか? os.listdir()はその両方を行います、どちらの値を返すかはディレクトリのパスを8ビット文字列で与えたか、あるいはUnicodeで与えたかによります。もしファイル名をUnicodeとして関数に渡せばファイル名はファイルシステムのエンコーディングを使ってデコードされ、Unicode文字列のリストが返されるでしょう。一方8ビットのパスを与えれば8ビットバージョンのファイル名が返されます。たとえば、デフォルトのファイルシステム・エンコーディングがUTF-8と仮定し、以下のプログラムを実行します:

fn = u'filename\u4500abc'
f = open(fn, 'w')
f.close()

import os
print os.listdir('.')
print os.listdir(u'.')

すると出力は以下のようになります:

amk:~$ python t.py
['.svn', 'filename\xe4\x94\x80abc', ...]
[u'.svn', u'filename\u4500abc', ...]

最初のリストはUTF-8でエンコードされたファイル名を含みます。次のリストはそのUnicode版を含みます。

Unicode対応のプログラムを書くためのTips

この章ではUnicodeを扱うプログラムを書く上でのいくつかの提案をしています。

最も重要なTip:

ソフトウェアの内部では常にUnicode文字列だけを使って処理を行い、特定のエンコーディングに 変換してから出力しなければならない。

Unicodeと8ビット文字列の両方に対応する関数を作ろうとしてみれば、二つの文字列を混ぜた箇所ではどこでも、あなたのプログラムがバグに対して脆弱であることに気づくことでしょう。PythonのデフォルトエンコーディングはASCIIなので、入力に127より大きい値を持つ文字が与えられたら常にUnicodeDecodeErrorが起こるようになっています、その文字はASCIIでは扱えないからです。

あなたが自分のソフトウェアにアクセント付き文字を含まないデータでのテストしか行わなかったならば、このような問題は簡単に見過ごされます;全てはうまく動いているように見えますが、あなたのプログラムには最初に127より大きい文字を使おうとするユーザが現れるのを待つバグがあるのです。よって2つ目のTipは次のようになります:

テストデータに127より大きな値を持つ文字を含めよ。255より大きければもっとよい。

ブラウザやその他の信頼できないソースから受け取ったデータを利用するときに使われる一般的な手法は、そこからコマンドラインを生成して使ったりデータベースに保存する前にあらかじめ不正な文字についてチェックすることです。もしこれを行っているならば、一旦使用したり保存したりする形式にした後に注意深く文字列をチェックしたほうがよいでしょう;エンコーディングは文字を隠すために使うことも可能なのです。もし入力データがエンコーディングも指定してくる場合、これは特に正しいです。多くのエンコーディングが、よくチェックされる文字については手を加えません。しかしPythonは'base64' のような全ての文字を変化させてしまうエンコーディングも含んでいます。

たとえば、あなたがUnicodeファイル名を使えるコンテンツ管理システムを運用していて、'/'を含んだ名前を不許可にしたいとします。あなたはこんなコードを書くかもしれません:

def read_file (filename, encoding):
    if '/' in filename:
        raise ValueError("'/' not allowed in filenames")
    unicode_name = filename.decode(encoding)
    f = open(unicode_name, 'r')
    # ... return contents of file ...

しかしながら、もし攻撃者がエンコーディングを'base64'に指定できれば'L2V0Yy9wYXNzd2Q='という文字列、これはbase64で暗号化された'/etc/passwd'ですが、この文字列を使ってシステムファイルを読むことができます。上のコードは文字'/'をエンコードされた文字列から探しているので、デコード結果からの危険な文字を見逃してしまいます。

リファレンス

Marc-André Lemburg氏の発表"Writing Unicode-aware Applications in Python"のPDFスライドが<http://www.egenix.com/files/python/LSM2005-Developing-Unicode-aware-applications-in-Python.pdf>で入手できます。文字エンコーディングについての疑問のほか、どうやってアプリケーションを国際化、地域言語化するのかについても述べられています。

更新履歴と謝辞

次に述べる全ての人に、間違いを正してくれたことや助言をしてくれたことに対して感謝いたします。: Nicholas Bastin, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André Lemburg, Martin von Löwis.

Version 1.0: posted August 5 2005.

Version 1.01: posted August 7 2005. 事実関係とタグ訂正、リンク追加

Version 1.02: posted August 16 2005. 事実関係の訂正


訳: 冨岡悠 <u atmark mail dot ne dot jp>