PythonでUnicodeを扱う方法
著者は「Write for Donations」プログラムの一環として、寄付を受けるためにフリーでオープンソースの基金を選択しました。
イントロダクション
Unicodeは、世界のほとんどのコンピュータにおける標準的な文字エンコーディングです。それは、文字や記号、絵文字、制御文字を含むテキストが異なるデバイス、プラットフォーム、デジタルドキュメント上でもオペレーティングシステムやソフトウェアに関係なく同じように表示されることを保証します。これはインターネットやコンピューティング業界において重要な役割を果たしており、それがなければインターネットは混沌としたものになり、使用が困難となるでしょう。
Unicode自体はエンコーディングではありませんが、地球上のほぼすべての文字のデータベースのようなものです。Unicodeはデータベース内の各文字に対する識別子であるコードポイントを含んでおり、その値は0から110万までの範囲を持ちます。つまり、これらのユニークなコードポイントがすぐに枯渇することは非常にありそうもありません。UnicodeのすべてのコードポイントはU+nという形で表されます。ここで、U+はUnicodeコードポイントであることを示し、nは文字のための4から6桁の16進数の集合です。ASCIIと比べて、Unicodeはより堅牢なエンコーディングシステムです。ASCIIは128文字しか表現できませんが、Unicodeはほぼ15万の文字を持ち、地球上のすべての言語の文字をカバーしています。したがって、ASCIIでデジタルテキストを世界中で交換することは困難でしたが、Unicodeならアクセントの付いた文字もサポートされます。
これには、Pythonなどのプログラミング言語がテキストを適切に扱い、ソフトウェアが国際化を実現できるようにする必要があります。Pythonは、電子メールからサーバー、ウェブまで幅広い用途に使用することができ、Unicodeを優雅に扱う方法を持っています。これは、Pythonが文字列に対してUnicode標準を採用することで実現しています。
PythonでUnicodeを扱うことは、混乱を生み、エラーを引き起こす可能性があります。このチュートリアルでは、UnicodeをPythonで使用する基本を提供し、これらの問題を回避するための手助けをします。Pythonを使用してUnicodeを解釈し、Pythonの正規化関数を使用してデータを正規化し、PythonのUnicodeエラーを処理します。
前提条件
このチュートリアルに従うためには、以下のものが必要です。
- Python installed locally or on a remote server. If you do not already have Python set up, you can do so by following our tutorial How To Install Python 3 and Set Up a Programming Environment. Choose the version that is appropriate for your Linux distribution.
- Familiarity with basic Python programming and Python’s string methods
- Knowledge of how to work with the Python Interactive Console
ステップ1 – PythonでUnicodeのコードポイントを変換する
エンコーディングとは、データをコンピュータが読み取れる形式で表現するプロセスのことです。ASCIIやLatin-1など、データをエンコードするためのさまざまな方法がありますが、おそらく最も一般的なのはUTF-8でしょう。UTF-8は、世界中の文字を1つの文字セットで表現できるエンコーディングの一種です。そのため、国際化されたデータを扱う人にとっては、欠かせないツールです。一般的には、ほとんどの用途においてUTF-8は良い選択です。比較的効率的で、さまざまなソフトウェアと使用することができます。UTF-8はUnicodeのコードポイントを16進数のバイトに変換し、コンピュータが理解できるようにします。つまり、Unicodeがマッピングであり、UTF-8がそのマッピングをコンピュータが理解できるようにする仕組みです。
Python 3では、デフォルトの文字列エンコーディングはUTF-8であり、つまりPythonの文字列内の任意のUnicodeコードポイントは自動的に対応する文字に変換されます。
この手順では、PythonでUnicodeコードポイントを使用して著作権記号(©)を作成します。まず、ターミナルでPythonのインタラクティブコンソールを起動し、次のように入力します。
>>> s = '\u00A9'
>>> s
前のコードでは、Unicodeのコードポイント\u00A9を持つ文字列sを作成しました。前述の通り、Pythonの文字列はデフォルトでUTF-8エンコーディングを使用しているため、sの値を表示すると自動的に対応するUnicodeの記号に変換されます。なお、コードポイントの前にある\uは必要です。これがないと、Pythonはコードポイントを変換できません。前のコードの出力は対応するUnicodeの記号を返します。
‘©’
Pythonプログラミング言語には、文字列をエンコードおよびデコードするための組み込み関数が用意されています。encode() 関数は、文字列をバイト文字列に変換します。
これを証明するために、Pythonのインタラクティブコンソールを開き、次のコードを入力してください。
>>> '🅥'.encode('utf-8')
以下のコードは、文字のバイト文字列を出力します。
b’\xf0\x9f\x85\xa5′
各バイトの前に「\x」が付いていることに注意してください。これはそれが16進数であることを示しています。
Note
次に、decode()関数を使用してバイト文字列を文字列に変換します。decode()関数は、エンコーディングタイプを引数として受け入れます。また、decode()関数は、文字列の先頭にbを使用して指定されたバイト文字列のみをデコードできることを言及する価値があります。bを削除すると、AttributeErrorが発生します。
コンソールに入力してください:
>>> b'\xf0\x9f\x85\xa5'.decode('utf-8')
コードはこのような出力を返します。 (Kōdo wa kono yōna shutsuryoku o kaeshimasu.)
‘🅥’
あなたは現在、PythonにおけるUnicodeの解釈について基本的な理解を持っています。次に、Pythonの組み込みのunicodedataモジュールを使用して、文字列に対して高度なUnicode技術を実行していきます。
ステップ2- PythonにおけるUnicodeの正規化
このステップでは、PythonでUnicodeを正規化します。正規化により、異なるフォントで書かれた2つの文字が同じかどうかを判定することができます。これは、異なるコードポイントを持つ2つの文字が同じ結果を生む場合に便利です。例えば、Unicodeの文字Rとℜは、人の目には同じであり、どちらもRの文字ですが、コンピュータはそれらを異なる文字とみなします。
以下のコード例はこれを更に実証します。Pythonコンソールを開き、以下を入力してください。
>>> styled_R = 'ℜ'
>>> normal_R = 'R'
>>> styled_R == normal_R
以下の出力結果を受け取ります。 (Shimashou)
False
このコードは、Pythonの文字列は2つの文字を同一と見なさないため、出力はFalseとなります。Unicodeを扱う際に正規化が重要な理由は、このような区別ができるからです。
Unicodeでは、いくつかの文字は2つ以上の文字を組み合わせて作られます。この場合、正規化は重要です。正規化により、文字列が互いに一貫性を持つことが保たれます。この概念をより良く理解するために、Pythonのコンソールを開いて以下のコードを入力してください。
>>> s1 = 'hôtel'
>>> s2 = 'ho\u0302tel'
>>> len(s1), len(s2)
前のコードでは、文字列s1に「ô」の文字を含むように作成し、2行目では文字列s2にはサーカムフレックス文字(̂ )のコードポイントが含まれています。実行後、コードは以下の出力を返します。
(5, 6)
先行する出力は、2つの文字列が同じ文字で構成されているが、長さが異なることを示しており、そのため等価性のテストには失敗することを意味します。同じコンソールに以下を入力してテストしてください。
>>> s1 == s2
コードは以下の出力を返します。 (The code returns the following output.)
False
文字列変数s1とs2は同じUnicode文字を生成するものの、長さが異なるために等しくありません。
次のステップでは、normalize()関数を使用してこの問題を解決することができます。
ステップ3 — NFD、NFC、NFKD、およびNFKCという方法でUnicodeを正規化する
このステップでは、Pythonのunicodedataライブラリのunicodedataモジュールで、文字の検索と正規化機能を提供するnormalize()関数を使用して、Unicode文字列を標準化します。normalize()関数は、最初の引数として正規化形式、2番目の引数として正規化される文字列を取ることができます。Unicodeには、NFD、NFC、NFKD、NFKCの4つの正規化形式があります。
NFD正規化形式は、文字を複数の結合文字に分解します。これによってテキストがアクセントに対して無感応になり、検索やソートに役立ちます。これを行うには、文字列をバイトにエンコードすることができます。
コンソールを開いて、次のコマンドを入力してください。
>>> from unicodedata import normalize
>>> s1 = 'hôtel'
>>> s2 = 'ho\u0302tel'
>>> s1_nfd = normalize('NFD', s1)
>>> len(s1), len(s1_nfd)
以下の出力がコードで生成されます。
(5, 6)
以下の例が示す通り、文字列s1を正規化すると、その長さは1文字増えます。これは、ôシンボルが2つの文字(oとˆ)に分割されるためです。以下のコードを使用することで確認できます。
>>> s1.encode(), s1_nfd.encode()
結果の出力は、正規化された文字列をエンコードした後、文字列s1_nfdにおいてoの文字がˆの文字から分離されたことを示しています。
(b’h\xc3\xb4tel’, b’ho\xcc\x82tel’)
NFC正規化形式は、まず文字を分解し、それから使用可能な結合文字で再構成します。NFCは、最も短い出力を生成するように文字列を組み立てるため、W3CはウェブでNFCを使用することを推奨しています。キーボードの入力はデフォルトで結合文字を返すため、その場合にはNFCを使用することが良いアイデアです。
例として、次のテキストをインタラクティブなコンソールに入力してください。
>>> from unicodedata import normalize
>>> s2_nfc = normalize('NFC', s2)
>>> len(s2), len(s2_nfc)
コードは以下の出力を生成します。
(6, 5)
例えば、文字列s2を正規化することで長さが1減ります。以下のコードをインタラクティブコンソールで実行することで、それを確認することができます。
>>> s2.encode(), s2_nfc.encode()
コードの出力は次のようになります。
(b’ho\xcc\x82tel’, b’h\xc3\xb4tel’)
出力結果は、oとˆの文字が1つのô文字に統合されたことを示しています。
NFKDおよびNFKC正規化形式は、「厳密な」正規化に使用され、Unicode文字列の検索やパターンマッチングと関連するさまざまな問題に使用することができます。NFKDおよびNFKCの「K」は互換性を表しています。
NFDおよびNFCの正規化形式は、文字を分解しますが、NFKDおよびNFKCは似ていないが同等の文字に対して互換性分解を実行し、任意の書式の違いを取り除きます。たとえば、文字列「②①」は「21」と似ていませんが、両方とも同じ値を表しています。NFKCおよびNFKDの正規化形式は、これらの文字から書式(この場合は数字の周りの円)を取り除いて、最もシンプルな形式を提供します。
以下の例は、NFDとNFKDの正規化の形式の違いを示しています。Pythonのインタラクティブコンソールを開いて、以下のように入力してください。
>>> s1 = '2⁵ô'
>>> from unicodedata import normalize
>>> normalize('NFD', s1), normalize('NFKD', s1)
以下の出力結果を取得します。
(‘2⁵ô’, ’25ô’)
以下のコードを実行することで、出力結果から分かるように、文字列s1内の指数文字はNFD形式では分解できませんが、NFKD形式では指数の書式が取り除かれ、互換性文字(この場合は指数の5)がそれに相当する数値(5という数字)に置き換えられました。NFDとNFKDの正規化形式はまだ文字を分解しているため、先ほどのNFDの例で見たように、”ô”の文字は長さが1増えるはずです。これを確認することができます。
>>> len(normalize('NFD', s1)), len(normalize('NFKD', s1))
以下の結果がコードから返されます。
(4, 4)
NFKC正規化形式は似たような方法で動作しますが、文字を分解する代わりに組み立てます。同じPythonコンソールで次のように入力してください。
>>> normalize('NFC', s1), normalize('NFKC', s1)
コードは以下を返します。
(‘2⁵ô’, ’25ô’)
NFKCは合成アプローチに従っているため、分解の場合には文字列が1文字短くなることを期待するべきです。次のコードを実行することで、これを確認することができます。
>>> len(normalize('NFC', s1)), len(normalize('NFKC', s1))
次の出力が返されます。
(3, 3)
前の手順を実行することで、正規化の種類とその違いについての作業知識が得られます。次のステップでは、PythonでのUnicodeエラーを解決します。
ステップ4−PythonでUnicodeエラーを解決する
PythonでUnicodeを処理する際には、UnicodeEncodeErrorとUnicodeDecodeErrorの2種類のUnicodeエラーが発生する可能性があります。これらのUnicodeエラーは混乱を引き起こすこともありますが、管理することができ、この段階で両方のエラーを修正します。
UnicodeEncodeErrorの解決
Unicodeでのエンコードは、特定のエンコードを使用してUnicode文字列をバイトに変換するプロセスです。UnicodeEncodeErrorは、指定されたエンコードでは表現できない文字が含まれている文字列をエンコードしようとした時に発生します。
ASCII文字集合に含まれていない文字を含む文字列をエンコードすることで、このエラーを作成します。
コンソールを開き、次のように入力してください。
(Konsōru o hiraki, tsugi no yō ni nyūryoku shite kudasai.)
>>> ascii_supported = '\u0041'
>>> ascii_supported.encode('ascii')
以下はあなたの出力です。
b’A’
それから、以下を入力してください:
>>> ascii_unsupported = '\ufb06'
>>> ascii_unsupported.encode('utf-8')
以下の出力が得られます。
b’\xef\xac\x86′
最終的に、以下をタイプしてください。
>>> ascii_unsupported.encode('ascii')
このコードを実行すると、次のエラーが発生します。
Traceback (most recent call last): File “<stdin>”, line 1, in <module> UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\ufb06’ in position 0: ordinal not in range(128)
ASCIIには限られた数の文字しか存在せず、ASCII文字集合に含まれていない文字がPythonで見つかるとエラーが発生します。ASCII文字集合はコードポイント\ufb06を認識しないため、Pythonは128しかないというエラーメッセージを返し、このコードポイントの十進数の相当値がその範囲内にないことを示します。
encode()関数のerrors引数を使用することで、UnicodeEncodeErrorを処理できます。errors引数には、ignore、replace、xmlcharrefreplaceの3つの値を指定できます。
コンソールを開いて、以下を入力してください。
>>> ascii_unsupported = '\ufb06'
>>> ascii_unsupported.encode('ascii', errors='ignore')
以下の出力結果が得られます。
b”
次に以下を入力してください。
>>> >>> ascii_unsupported.encode('ascii', errors='replace')
出力は以下の通りです。
b’?’
最後に、以下を入力してください。
>>> ascii_unsupported.encode('ascii', errors='xmlcharrefreplace')
出力は:
b’st’
すべての場合において、Pythonはエラーを表示しません。
前の例で示されているように、ignoreはエンコードできない文字を無視し、replaceは文字を?で置き換え、xmlcharrefreplaceはエンコードできない文字をXMLエンティティで置き換えます。
UnicodeDecodeErrorの解決
指定されたエンコーディングでは表現できない文字が含まれている文字列をデコードしようとすると、UnicodeDecodeErrorが発生します。
このエラーを発生させるために、エンコーディングでデコードできないバイト文字列をデコードしようと試みます。
コンソールを開き、次のように入力してください。 (Konsōru o hiraki, tsugi no yō ni nyūryoku shite kudasai.)
>>> iso_supported = '§'
>>> b = iso_supported.encode('iso8859_1')
>>> b.decode('utf-8')
以下のエラーが発生します。
Traceback (most recent call last): File “<stdin>”, line 1, in <module> UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xa7 in position 0: invalid start byte
このエラーに遭遇した場合、文字列をデコードする際にはdecode()関数内のerrors引数を使用することができます。この引数は、ignoreとreplaceの2つの値を受け付けます。
これを証明するために、Pythonコンソールを開き、次のコードを入力してください。
>>> iso_supported = '§A'
>>> b = iso_supported.encode('iso8859_1')
>>> b.decode('utf-8', errors='replace')
あなたの出力結果は次のようになります:
‘�A’
次に、以下を入力してください。
>>> b.decode('utf-8', errors='ignore')
あなたの出力は次のようになります:
‘A’
前述の例では、decode()関数でreplace値を使用すると「�」文字が追加され、ignoreを使用するとデコーダ(この場合はutf-8)がバイトをデコードできない場合は何も返されません。
文字列を解読する際は、エンコーディングが何であるかを想定できないことに注意してください。文字列を解読するには、それがどのようにエンコードされたかを知る必要があります。
結論
この記事では、PythonでUnicodeを使用する基本的な方法について説明しました。文字列をエンコードおよびデコードし、NFD、NFC、NFKD、NFKCを使用してデータを正規化し、Unicodeエラーを解決しました。また、ソートや検索に正規化形を使用するシナリオについても説明しました。これらのテクニックはPythonを使用してUnicodeの問題を解決するのに役立ちます。次のステップとして、unicodedataモジュールのドキュメンテーションを読んで、このモジュールが提供するその他の機能について学ぶことができます。Pythonでプログラミングをさらに探求するためには、当社のチュートリアルシリーズ「Python 3でのコーディング方法」を読んでみてください。