詳しくはPython 2.xにおけるUnicode符号化の使用

11058 ワード

UnicodeやPythonについての説明はたくさんあると思いますが、自分の理解しやすいように、それらについてもっと書くつもりです.
バイトストリームvs Unicodeオブジェクト
まずPythonで文字列を定義します.stringタイプを使用すると、実際にはバイト列が格納されます. 

[ a ][ b ][ c ] = "abc"
[ 97 ][ 98 ][ 99 ] = "abc"

この例ではabcという文字列はバイト列です.97.,98,,99はASCIIコードです.Python 2.xバージョンの欠点の1つは、デフォルトですべての文字列をASCIIとして扱うことです.残念なことに、ASCIIはラテン文字セットの中で最も一般的な基準です.
ASCIIは、最初の127個の数字で文字マッピングを行います.Windows-1252やUTF-8のような文字には、同じ上位127文字がマッピングされています.あなたの文字列の各バイトの値が127未満の場合、安全なハイブリッド文字列符号化です.しかし、この仮定をするのは危険なことであり、以下に述べる.
文字列にバイトの値が126より大きいと問題が発生します.Windows-1252で符号化された文字列を見てみましょう.Windows-1252の文字マッピングは8ビットの文字マッピングで、256文字になります.最初の127文字はASCIIと同じで、次の127文字はwindows-1252によって定義された他の文字です. 

A windows-1252 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 150 ] = "abc�C"

Windows-1252は依然としてバイト列ですが、最後のバイトの値が126より大きいのを見ましたか.PythonがデフォルトのASCII規格でこのバイトストリームを復号しようとすると、エラーが発生します.Pythonがこの文字列を復号するとどうなるかを見てみましょう.

>>> x = "abc" + chr(150)
>>> print repr(x)
'abc\x96'
>>> u"Hello" + x
Traceback (most recent call last):
 File "", line 1, in ?
UnicodeDecodeError: 'ASCII' codec can't decode byte 0x96 in position 3: ordinal not in range(128)

UTF-8を使用して別の文字列を符号化します.

A UTF-8 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc�C"
[0x61] [0x62] [0x63] [0xe2] [ 0x80] [ 0x93] = "abc-"

おなじみのUnicodeコードテーブルを手に取ると、英語のダッシュボードに対応するUnicodeコードポイントが8211(0)であることがわかります.×2013).この値はASCII最大127より大きい.1バイト以上の値を格納できます.なぜなら8211(0)×2013)は2バイトで、UTF-8はいくつかのテクニックを利用して、1文字を保存するには3バイトが必要だとシステムに伝えなければならない.また、PythonがデフォルトのASCIIで126より大きい文字の値を持つUTF-8符号化文字列を符号化する準備をしている場合を見てみましょう. 

>>> x = "abc\xe2\x80\x93"
>>> print repr(x)
'abc\xe2\x80\x93'
>>> u"Hello" + x
Traceback (most recent call last):
 File "", line 1, in ?
UnicodeDecodeError: 'ASCII' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)

PythonはASCII符号化をデフォルトで使用していることがわかります.4文字目を処理すると、226が126より大きいため、Pythonはエラーを投げ出した.これがハイブリッド符号化による問題である.
デコードバイトストリーム
Python Unicodeの勉強を始めたとき、この用語を復号するのは疑問に思うかもしれません.バイトストリームをUnicodeオブジェクトに復号し、Unicodeオブジェクトをバイトストリームに符号化できます.
Pythonは、バイトストリームをUnicodeオブジェクトに復号する方法を知る必要があります.バイトストリームを取得すると、その「復号方法」を呼び出してUnicodeオブジェクトを作成します.
できるだけ早くバイトストリームをUnicodeに復号したほうがいいです. 

>>> x = "abc\xe2\x80\x93"
>>> x = x.decode("utf-8")
>>> print type(x)

>>> y = "abc" + chr(150)
>>> y = y.decode("windows-1252")
>>> print type(y)
>>> print x + y
abc�Cabc�C

Unicodeをバイトストリームにエンコード
Unicodeオブジェクトはテキストの符号化不可知論の代表である.Unicodeオブジェクトを簡単に出力することはできません.出力前にバイト列にする必要があります.PythonはUnicodeをバイトストリームに符号化する際にASCIIがデフォルトで適用されるが、このデフォルトの動作は多くの悩ましい問題の原因になるだろう. 

>>> u = u"abc\u2013"
>>> print u
Traceback (most recent call last):
 File "", line 1, in 
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2013' in position 3: ordinal not in range(128)
>>> print u.encode("utf-8")
abc�C

codecsモジュールの使用
codecsモジュールは、バイトストリームを処理する際に役立ちます.定義された符号化でファイルを開くことができ、ファイルから読み込んだ内容が自動的にUnicodeオブジェクトに変換されます.
これを試してみてください.

>>> import codecs
>>> fh = codecs.open("/tmp/utf-8.txt", "w", "utf-8")
>>> fh.write(u"\u2013")
>>> fh.close()

Unicodeオブジェクトを取得しutf-8符号化でファイルに書き込むことです.他の状況でもこのように使用することができます.
これを試してみてください.
1つのファイルからデータを読み出すとcodecs.Openはutf-8符号化ファイルを自動的にUnicodeオブジェクトに変換できるファイルオブジェクトを作成します.
上の例に続いて、今回はurllibストリームを使用します. 

>>> stream = urllib.urlopen("http://www.google.com")
>>> Reader = codecs.getreader("utf-8")
>>> fh = Reader(stream)
>>> type(fh.read(1))

>>> Reader


単行バージョン:

>>> fh = codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com"))
>>> type(fh.read(1))

codecsモジュールに十分注意しなければなりません.あなたが伝えたものはUnicodeオブジェクトでなければなりません.そうしないと、バイトストリームは自動的にASCIIとして復号されます. 

>>> x = "abc\xe2\x80\x93" # our "abc-" utf-8 string
>>> fh = codecs.open("/tmp/foo.txt", "w", "utf-8")
>>> fh.write(x)
Traceback (most recent call last):
File "", line 1, in 
File "/usr/lib/python2.5/codecs.py", line 638, in write
 return self.writer.write(data)
File "/usr/lib/python2.5/codecs.py", line 303, in write
 data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 3: ordinal not in range(128)

あら、私は行きます.PythonはまたASCIIですべてを復号し始めました.UTF-8バイトストリームをスライスする問題
UTF-8符号化列がバイトリストであるため、len()とスライス操作は正常に動作しません.まず、私たちが前に使った文字列を使います. 

[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc�C"

次のようにします.

>>> my_utf8 = "abc�C"
>>> print len(my_utf8)
6

神馬?4文字のように見えますが、lenの結果は6です.lenは文字数ではなくバイト数を計算するからです. 

>>> print repr(my_utf8)
'abc\xe2\x80\x93'

この文字列を分割します. 

>>> my_utf8[-1] # Get the last char
'\x93'

私は行きます.切り分けの結果は最後のバイトで、最後の文字ではありません.
UTF-8を正しく分割するには、バイトストリームを復号してUnicodeオブジェクトを作成することが望ましい.そして安全な操作とカウントができます. 

>>> my_unicode = my_utf8.decode("utf-8")
>>> print repr(my_unicode)
u'abc\u2013'
>>> print len(my_unicode)
4
>>> print my_unicode[-1]
�C

Pythonが自動的に符号化/復号化されると
場合によっては、Pythonが自動的にASCIIを使用して符号化/復号を行う場合、エラーが投げ出されることがある.
最初の例は、Unicodeとバイト列を統合しようとしたときです. 

>>> u"" + u"\u2019".encode("utf-8")
Traceback (most recent call last):
 File "", line 1, in 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0:  ordinal not in range(128)

リストをマージするときに同じことが起こります.PythonはリストにstringとUnicodeオブジェクトがある場合に自動的にバイト列をUnicodeに復号します. 

>>> ",".join([u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8")])
Traceback (most recent call last):
 File "", line 1, in 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 11: ordinal not in range(128)

または、バイト列をフォーマットしてみます.

>>> "%s
%s" % (u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8"),) Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 11: ordinal not in range(128)

基本的にUnicodeとバイト列を混ぜて使うと、エラーが発生します.
この例ではutf-8ファイルを作成し、Unicodeオブジェクトのテキストを追加します.UnicodeDecodeErrorエラーが表示されます. 

>>> buffer = []
>>> fh = open("utf-8-sample.txt")
>>> buffer.append(fh.read())
>>> fh.close()
>>> buffer.append(u"This string\u2019s unicode")
>>> print repr(buffer)
['This file\xe2\x80\x99s got utf-8 in it
', u'This string\u2019s unicode'] >>> print "
".join(buffer) Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 9: ordinal not in range(128)

codecsモジュールを使用してファイルをUnicodeとしてロードしてこの問題を解決することができます. 

>>> import codecs
>>> buffer = []
>>> fh = open("utf-8-sample.txt", "r", "utf-8")
>>> buffer.append(fh.read())
>>> fh.close()
>>> print repr(buffer)
[u'This file\u2019s got utf-8 in it
', u'This string\u2019s unicode'] >>> buffer.append(u"This string\u2019s unicode") >>> print "
".join(buffer) This file's got utf-8 in it This string's unicode

ご覧の通りcodecs.Openによって作成されたストリームは、データが読み出されると自動的にビット列をUnicodeに変換する.
ベストプラクティス
1.最初の復号、最後の符号化
2.utf-8符号化のデフォルト使用
3.codecsとUnicodeオブジェクトを使用して処理を簡略化
最初の復号化は、バイトストリーム入力がいつあっても、入力をUnicodeに早期に復号する必要があることを意味する.これによりlen()とutf-8バイトストリームの分割に問題が発生することを防止します.
最後の符号化は、テキストをある場所に出力しようとした場合にのみ、バイトストリームに符号化することを意味します.この出力はファイル、データベース、socketなどかもしれません.unicodeオブジェクトは、処理が完了した後にのみ符号化されます.最後の符号化は、PythonにUnicodeオブジェクトを符号化させないことを意味します.PythonはASCIIコードを使用し、プログラムがクラッシュします.
デフォルトでUTF-8符号化を使用すると、UTF-8はUnicode文字を処理できるので、windows-1252とASCIIの代わりに使用したほうがいいことを意味します.
codecsモジュールは、ファイルやsocketのようなストリームを処理するときにピットを踏まないようにすることができます.codecsが提供するこのツールがない場合は、ファイルの内容をバイトストリームとして読み込み、このバイトストリームをUnicodeオブジェクトに復号する必要があります.
codecsモジュールは、バイトストリームをUnicodeオブジェクトに迅速に変換し、多くの面倒を省くことができます.
解釈UTF-8
最後の部分はUTF-8について入門することができます.もしあなたがスーパー極客であれば、この部分を無視することができます.
UTF−8を使用すると、127〜255の間の任意のバイトが特別である.これらのバイトは、システムにこれらのバイトがマルチバイトシーケンスの一部であることを示す. 

Our UTF-8 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc�C"

最後の3バイトはUTF-8マルチバイトシーケンスです.この3バイトの最初のバイトを2進数に変換すると、次のような結果が得られます.

11100010

最初の3ビットは、3バイトシーケンス226181147を開始したとシステムに伝えた.
では、完全なバイトシーケンスです. 

11100010 10000000 10010011

3バイトシーケンスに次のマスクを使用します.(詳しくはこちらを参照)

 
1110xxxx 10xxxxxx 10xxxxxx
XXXX0010 XX000000 XX010011 Remove the X's
0010    000000  010011 Collapse the numbers
00100000 00010011     Get Unicode number 0x2013, 8211 The "�C"

ここではUTF-8の入門に関する基本的な知識だけで、もっと詳しく知りたいなら、UTF-8のウィキペディアページを見てもいいです.