Unicode HOWTO —Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.9/howto/unicode
移動先:案内検索

Unicode HOWTO

リリース
1.12

このHOWTOでは、テキストデータを表現するためのUnicode仕様に対するPythonのサポートについて説明し、Unicodeを操作しようとするときに一般的に遭遇するさまざまな問題について説明します。

Unicodeの概要

定義

今日のプログラムは、多種多様なキャラクターを処理できる必要があります。 多くの場合、アプリケーションは国際化されており、ユーザーが選択できるさまざまな言語でメッセージを表示して出力します。 同じプログラムで、英語、フランス語、日本語、ヘブライ語、またはロシア語でエラーメッセージを出力する必要がある場合があります。 Webコンテンツは、これらの言語のいずれかで作成でき、さまざまな絵文字記号を含めることもできます。 Pythonの文字列型は、文字を表すためにUnicode標準を使用します。これにより、Pythonプログラムはこれらのさまざまな可能な文字すべてを処理できます。

Unicode( https://www.unicode.org/ )は、人間の言語で使用されるすべての文字をリストし、各文字に独自のコードを与えることを目的とした仕様です。 Unicode仕様は継続的に改訂および更新され、新しい言語と記号が追加されています。

文字は、テキストの可能な限り最小のコンポーネントです。 「A」、「B」、「C」などはすべて異なる文字です。 'È'と 'Í'もそうです。 文字はあなたが話している言語や文脈によって異なります。 たとえば、大文字の「I」とは別の「RomanNumeralOne」の文字「Ⅰ」があります。 通常は同じように見えますが、これらは異なる意味を持つ2つの異なる文字です。

Unicode標準では、文字がコードポイントでどのように表されるかが説明されています。 コードポイント値は、0〜0x10FFFFの範囲の整数です(約110万の値、割り当てられた実際の数はそれよりも小さい)。 標準およびこのドキュメントでは、コードポイントは、値0x265e(10進数で9,822)の文字を意味する表記U+265Eを使用して記述されています。

Unicode標準には、文字とそれに対応するコードポイントを一覧表示する多くのテーブルが含まれています。

0061    'a'; LATIN SMALL LETTER A
0062    'b'; LATIN SMALL LETTER B
0063    'c'; LATIN SMALL LETTER C
...
007B    '{'; LEFT CURLY BRACKET
...
2167    'Ⅷ'; ROMAN NUMERAL EIGHT
2168    'Ⅸ'; ROMAN NUMERAL NINE
...
265E    '♞'; BLACK CHESS KNIGHT
265F    '♟'; BLACK CHESS PAWN
...
1F600   '😀'; GRINNING FACE
1F609   '😉'; WINKING FACE
...

厳密には、これらの定義は、「これは文字U+265E」と言っても意味がないことを意味します。 U+265Eは、特定の文字を表すコードポイントです。 この場合、それは文字「BLACK CHESS KNIGHT」、「♞」を表します。 非公式のコンテキストでは、コードポイントと文字のこの区別は時々忘れられます。

文字は、グリフと呼ばれる一連のグラフィック要素によって画面または紙に表されます。 たとえば、大文字のAのグリフは、2つの斜めのストロークと1つの水平のストロークですが、正確な詳細は使用されているフォントによって異なります。 ほとんどのPythonコードは、グリフについて心配する必要はありません。 表示する正しいグリフを見つけることは、通常、GUIツールキットまたは端末のフォントレンダラーの仕事です。


エンコーディング

前のセクションを要約すると、Unicode文字列はコードポイントのシーケンスであり、0から0x10FFFF(10進数で1,114,111)までの数字です。 このコードポイントのシーケンスは、コードユニットのセットとしてメモリ内で表す必要があり、コードユニットは8ビットバイトにマップされます。 Unicode文字列をバイトシーケンスに変換するための規則は、文字エンコード、または単にエンコードと呼ばれます。

あなたが考えるかもしれない最初のエンコーディングは、コード単位として32ビット整数を使用し、次に32ビット整数のCPU表現を使用することです。 この表現では、文字列「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未満であるため、多くのスペースが0x00バイトで占められています。 上記の文字列は、ASCII表現に必要な6バイトと比較して、24バイトかかります。 RAM使用量の増加はそれほど重要ではありませんが(デスクトップコンピューターにはギガバイトのRAMがあり、文字列は通常それほど大きくありません)、ディスクとネットワークの帯域幅の使用量を4倍に増やすことは耐えられません。
  3. strlen()などの既存のC関数とは互換性がないため、新しいファミリのワイド文字列関数を使用する必要があります。

したがって、このエンコーディングはあまり使用されず、代わりに、UTF-8などのより効率的で便利な他のエンコーディングを選択します。

UTF-8は最も一般的に使用されるエンコーディングの1つであり、Pythonはデフォルトでそれを使用することがよくあります。 UTFは「UnicodeTransformationFormat」の略で、「8」は8ビット値がエンコーディングで使用されることを意味します。 (UTF-16およびUTF-32エンコーディングもありますが、UTF-8よりも使用頻度は低くなります。)UTF-8は次のルールを使用します。

  1. コードポイントが128未満の場合、対応するバイト値で表されます。
  2. コードポイントが> = 128の場合、2、3、または4バイトのシーケンスに変換されます。ここで、シーケンスの各バイトは128〜255です。

UTF-8にはいくつかの便利なプロパティがあります。

  1. これは、任意のUnicodeコードポイントを処理できます。
  2. Unicode文字列は、ヌル文字(U + 0000)を表す場合にのみ埋め込まれたゼロバイトを含む一連のバイトに変換されます。 つまり、UTF-8文字列はstrcpy()などのC関数で処理でき、文字列の終わりマーカー以外のゼロバイトを処理できないプロトコルを介して送信できます。
  3. ASCIIテキストの文字列も有効なUTF-8テキストです。
  4. UTF-8はかなりコンパクトです。 一般的に使用される文字の大部分は、1バイトまたは2バイトで表すことができます。
  5. バイトが破損または失われた場合、次のUTF-8でエンコードされたコードポイントの開始を判別して再同期することができます。 また、ランダムな8ビットデータが有効なUTF-8のように見える可能性はほとんどありません。
  6. UTF-8はバイト指向のエンコーディングです。 エンコーディングは、各文字が1バイト以上の特定のシーケンスで表されることを指定します。 これにより、UTF-16やUTF-32などの整数および単語指向のエンコーディングで発生する可能性のあるバイト順序の問題が回避されます。UTF-16やUTF-32では、文字列がエンコードされたハードウェアによってバイトのシーケンスが異なります。


参考文献

Unicodeコンソーシアムサイトには、Unicode仕様の文字チャート、用語集、およびPDFバージョンがあります。 読みにくいものに備えてください。 Unicodeの起源と開発の年表もこのサイトで入手できます。

Computerphile Youtubeチャンネルで、TomScottがUnicodeとUTF-8 の歴史について簡単に説明します(9分36秒)。

標準を理解しやすくするために、JukkaKorpelaはUnicode文字テーブルを読み取るための入門ガイドを作成しました。

別の優れた紹介記事は、JoelSpolskyによって書かれました。 この紹介で物事が明確にならなかった場合は、続行する前にこの代替記事を読んでみてください。

ウィキペディアのエントリはしばしば役に立ちます。 たとえば、「文字エンコード」および UTF-8 のエントリを参照してください。


PythonのUnicodeサポート

Unicodeの基本を学んだので、PythonのUnicode機能を見てみましょう。

文字列型

Python 3.0以降、言語の str タイプにはUnicode文字が含まれています。つまり、"unicode rocks!"'unicode rocks!'、またはトリプルクォート文字列構文を使用して作成された文字列はすべてUnicodeとして保存されます。

PythonソースコードのデフォルトのエンコーディングはUTF-8であるため、文字列リテラルにUnicode文字を含めることができます。

try:
    with open('/tmp/input.txt', 'r') as f:
        ...
except OSError:
    # 'File not found' error message.
    print("Fichier non trouvé")

補足:Python 3は、識別子でのUnicode文字の使用もサポートしています。

répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
    f.write("test\n")

エディタに特定の文字を入力できない場合、または何らかの理由でソースコードをASCIIのみのままにしておきたい場合は、文字列リテラルでエスケープシーケンスを使用することもできます。 (システムによっては、auエスケープの代わりに実際の大文字-デルタグリフが表示される場合があります。)

>>> "\N{GREEK CAPITAL LETTER DELTA}"  # Using the character name
'\u0394'
>>> "\u0394"                          # Using a 16-bit hex value
'\u0394'
>>> "\U00000394"                      # Using a 32-bit hex value
'\u0394'

さらに、 bytesdecode()メソッドを使用して文字列を作成できます。 このメソッドは、UTF-8などの encoding 引数を取り、オプションで errors 引数を取ります。

errors 引数は、入力文字列がエンコーディングのルールに従って変換できない場合の応答を指定します。 この引数の有効な値は、'strict'UnicodeDecodeError 例外を発生させる)、'replace'U+FFFDREPLACEMENT CHARACTERを使用)、[X132X ] (Unicode結果から文字を除外するだけ)、または'backslashreplace'\xNNエスケープシーケンスを挿入)。 次の例は違いを示しています。

>>> b'\x80abc'.decode("utf-8", "strict")  
Traceback (most recent call last):
    ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
  invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'

エンコーディングは、エンコーディングの名前を含む文字列として指定されます。 Pythonには約100の異なるエンコーディングが付属しています。 リストについては、 Standard Encodings のPythonライブラリリファレンスを参照してください。 一部のエンコーディングには複数の名前があります。 たとえば、'latin-1''iso_8859_1''8859 'はすべて同じエンコーディングの同義語です。

1文字のUnicode文字列は、 chr()組み込み関数を使用して作成することもできます。この関数は、整数を受け取り、対応するコードポイントを含む長さ1のUnicode文字列を返します。 逆の操作は、1文字のUnicode文字列を受け取り、コードポイント値を返す組み込みの ord()関数です。

>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344

バイトへの変換

bytes.decode()の反対のメソッドは、 str.encode()です。これは、要求されたでエンコードされたUnicode文字列の bytes 表現を返します。 ]エンコーディング

errors パラメーターは、 decode()メソッドのパラメーターと同じですが、さらにいくつかの可能なハンドラーをサポートします。 'strict''ignore'、および'replace'(この場合、暗号化できない文字の代わりに疑問符を挿入します)の他に、'xmlcharrefreplace'( XML文字参照を挿入)、backslashreplace\uNNNNエスケープシーケンスを挿入)、およびnamereplace\N{...}エスケープシーケンスを挿入)。

次の例は、さまざまな結果を示しています。

>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')  
Traceback (most recent call last):
    ...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
  position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'&#40960;abcd&#1972;'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'

使用可能なエンコーディングを登録およびアクセスするための低レベルのルーチンは、コーデックモジュールにあります。 新しいエンコーディングを実装するには、コーデックモジュールについても理解する必要があります。 ただし、このモジュールによって返されるエンコードおよびデコード関数は、通常、快適なものよりも低レベルであり、新しいエンコードの作成は特殊なタスクであるため、モジュールはこのHOWTOではカバーされません。


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

Pythonソースコードでは、\uエスケープシーケンスを使用して特定のUnicodeコードポイントを記述できます。その後に、コードポイントを示す4桁の16進数が続きます。 \Uエスケープシーケンスも同様ですが、4桁ではなく8桁の16進数が必要です。

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

127を超えるコードポイントにエスケープシーケンスを使用することは、少量では問題ありませんが、フランス語やその他のアクセントを使用する言語のメッセージを使用するプログラムのように、アクセント付きの文字を多数使用する場合は煩わしくなります。 chr()組み込み関数を使用して文字列をアセンブルすることもできますが、これはさらに面倒です。

理想的には、言語の自然なエンコーディングでリテラルを記述できるようにする必要があります。 次に、アクセント付き文字を自然に表示し、実行時に適切な文字を使用するお気に入りのエディターを使用して、Pythonソースコードを編集できます。

PythonはデフォルトでUTF-8でのソースコードの記述をサポートしていますが、使用されているエンコーディングを宣言すれば、ほとんどすべてのエンコーディングを使用できます。 これは、ソースファイルの1行目または2行目に特別なコメントを含めることによって行われます。

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

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

構文は、ファイルにローカルな変数を指定するためのEmacsの表記法に触発されています。 Emacsは多くの異なる変数をサポートしていますが、Pythonは「コーディング」のみをサポートしています。 -*-記号は、コメントが特別であることをEmacsに示します。 これらはPythonにとって重要ではありませんが、慣例です。 Pythonは、コメントでcoding: nameまたはcoding=nameを探します。

このようなコメントを含めない場合、使用されるデフォルトのエンコーディングは、すでに述べたようにUTF-8になります。 詳細については、 PEP 263 も参照してください。


Unicodeプロパティ

Unicode仕様には、コードポイントに関する情報のデータベースが含まれています。 定義された各コードポイントの情報には、文字の名前、カテゴリ、該当する場合は数値(ローマ数字などの数値概念を表す文字、3分の1や5分の4などの分数など)が含まれます。 双方向テキストでコードポイントを使用する方法など、表示関連のプロパティもあります。

次のプログラムは、いくつかの文字に関する情報を表示し、特定の1文字の数値を出力します。

import unicodedata

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

for i, c in enumerate(u):
    print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
    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

カテゴリコードは、文字の性質を説明する略語です。 これらは、「文字」、「数字」、「句読点」、「記号」などのカテゴリにグループ化され、サブカテゴリに分類されます。 上記の出力からコードを取得するには、'Ll'は「文字、小文字」、'No'は「数字、その他」、'Mn'は「マーク、非間隔」、[ X148X] は「シンボル、その他」です。 カテゴリコードのリストについては、Unicode文字データベースのドキュメント一般的なカテゴリ値のセクションを参照してください。


文字列の比較

同じ文字セットを異なるコードポイントのシーケンスで表すことができるため、Unicodeは文字列の比較を複雑にします。 たとえば、「ê」のような文字は、単一のコードポイントU + 00EAとして、またはU + 0065 U + 0302として表すことができます。これは、「e」のコードポイントの後に「COMBININGCIRCUMFLEXACCENT」のコードポイントが続きます。 。 これらは印刷時に同じ出力を生成しますが、1つは長さ1の文字列で、もう1つは長さ2です。

大文字と小文字を区別しない比較のツールの1つは、Unicode標準で記述されているアルゴリズムに従って文字列を大文字と小文字を区別しない形式に変換する casefold()文字列メソッドです。 このアルゴリズムは、小文字の「ss」のペアになるドイツ文字の「ß」(コードポイントU + 00DF)などの文字に対して特別な処理を行います。

>>> street = 'Gürzenichstraße'
>>> street.casefold()
'gürzenichstrasse'

2番目のツールは、 unicodedata モジュールの normalize()関数で、文字列をいくつかの通常の形式の1つに変換します。この関数では、文字の後に結合文字が続き、単一の文字に置き換えられます。 normalize()を使用して、2つの文字列が結合文字を異なる方法で使用している場合に、不等式を誤って報告しない文字列比較を実行できます。

import unicodedata

def compare_strs(s1, s2):
    def NFD(s):
        return unicodedata.normalize('NFD', s)

    return NFD(s1) == NFD(s2)

single_char = 'ê'
multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print('length of first string=', len(single_char))
print('length of second string=', len(multiple_chars))
print(compare_strs(single_char, multiple_chars))

実行すると、次のように出力されます。

$ python3 compare-strs.py
length of first string= 1
length of second string= 2
True

normalize()関数の最初の引数は、目的の正規化形式を指定する文字列です。これは、「NFC」、「NFKC」、「NFD」、および「NFKD」のいずれかになります。

Unicode標準では、ケースレス比較を行う方法も指定されています。

import unicodedata

def compare_caseless(s1, s2):
    def NFD(s):
        return unicodedata.normalize('NFD', s)

    return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())

# Example usage
single_char = 'ê'
multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'

print(compare_caseless(single_char, multiple_chars))

Trueが印刷されます。 (NFD()が2回呼び出されるのはなぜですか? casefold()が正規化されていない文字列を返す文字がいくつかあるため、結果を再度正規化する必要があります。 議論と例については、Unicode標準のセクション3.13を参照してください。)


Unicode正規表現

re モジュールでサポートされている正規表現は、バイトまたは文字列として提供できます。 \d\wなどの一部の特殊文字シーケンスは、パターンがバイトとして提供されるか文字列として提供されるかによって、意味が異なります。 たとえば、\dはバイト単位で[0-9]の文字と一致しますが、文字列では'Nd'カテゴリにあるすべての文字と一致します。

この例の文字列には、タイ語とアラビア数字の両方で書かれた57という数字があります。

import re
p = re.compile(r'\d+')

s = "Over \u0e55\u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))

\d+を実行すると、タイの数字と一致して印刷されます。 re.ASCII フラグを compile()に指定すると、\d+は代わりにサブストリング「57」と一致します。

同様に、\wはさまざまなUnicode文字に一致しますが、バイト単位の[a-zA-Z0-9_]のみ、または re.ASCII が指定されている場合、\sはいずれかのUnicodeに一致します。空白文字または[ \t\n\r\f\v]


参考文献

PythonのUnicodeサポートに関するいくつかの良い代替の議論は次のとおりです。

str タイプは、Pythonライブラリリファレンスのテキストシーケンスタイプ— str で説明されています。

unicodedata モジュールのドキュメント。

コーデックモジュールのドキュメント。

Marc-AndréLemburgは、EuroPython 2002で「PythonとUnicode」(PDFスライド)というタイトルのプレゼンテーションを行いました。 スライドは、Python 2のUnicode機能の設計の優れた概要です(Unicode文字列型はunicodeと呼ばれ、リテラルはuで始まります)。


Unicodeデータの読み取りと書き込み

Unicodeデータで機能するコードを記述したら、次の問題は入出力です。 Unicode文字列をプログラムに取り込むにはどうすればよいですか?また、Unicodeを保存または送信に適した形式に変換するにはどうすればよいですか?

入力ソースと出力先によっては、何もする必要がない場合があります。 アプリケーションで使用されているライブラリがUnicodeをネイティブにサポートしているかどうかを確認する必要があります。 たとえば、XMLパーサーはUnicodeデータを返すことがよくあります。 多くのリレーショナルデータベースもUnicode値の列をサポートしており、SQLクエリからUnicode値を返すことができます。

Unicodeデータは通常、ディスクに書き込まれる前、またはソケットを介して送信される前に、特定のエンコーディングに変換されます。 すべての作業を自分で行うことができます。ファイルを開き、そこから8ビットバイトのオブジェクトを読み取り、bytes.decode(encoding)でバイトを変換します。 ただし、手動によるアプローチはお勧めしません。

1つの問題は、エンコーディングのマルチバイトの性質です。 1つのUnicode文字は数バイトで表すことができます。 ファイルを任意のサイズのチャンク(たとえば、1024または4096バイト)で読み取りたい場合は、エラー処理コードを記述して、単一のUnicode文字をエンコードするバイトの一部のみが最後に読み取られる場合をキャッチする必要があります。チャンク。 1つの解決策は、ファイル全体をメモリに読み込んでからデコードを実行することですが、これにより、非常に大きなファイルを操作できなくなります。 2 GiBファイルを読み取る必要がある場合は、2GiBのRAMが必要です。 (実際には、少なくともしばらくの間、エンコードされた文字列とそのUnicodeバージョンの両方をメモリに保持する必要があるためです。)

解決策は、低レベルのデコードインターフェイスを使用して、部分的なコーディングシーケンスのケースをキャッチすることです。 これを実装する作業はすでに完了しています。組み込みの open()関数は、ファイルの内容が指定されたエンコーディングであると想定し、次のようなメソッドのUnicodeパラメーターを受け入れるファイルのようなオブジェクトを返すことができます。 read()および write()として。 これは、 str.encode()と同じように解釈される open() 'の encoding および errors パラメーターを介して機能します。および bytes.decode()

したがって、ファイルからUnicodeを読み取るのは簡単です。

with open('unicode.txt', encoding='utf-8') as f:
    for line in f:
        print(repr(line))

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

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

Unicode文字U+FEFFは、バイト順マーク(BOM)として使用され、ファイルのバイト順の自動検出を支援するために、ファイルの最初の文字として書き込まれることがよくあります。 UTF-16などの一部のエンコーディングでは、ファイルの先頭にBOMが存在することを想定しています。 このようなエンコーディングを使用すると、BOMは最初の文字として自動的に書き込まれ、ファイルが読み取られるときにサイレントにドロップされます。 これらのエンコーディングには、リトルエンディアンおよびビッグエンディアンエンコーディングの「utf-16-le」や「utf-16-be」など、1つの特定のバイト順序を指定し、BOMをスキップしないバリアントがあります。

一部の領域では、UTF-8でエンコードされたファイルの先頭に「BOM」を使用することも慣例となっています。 UTF-8はバイト順序に依存しないため、この名前は誤解を招く恐れがあります。 マークは、ファイルがUTF-8でエンコードされていることを示すだけです。 このようなファイルを読み取るには、「utf-8-sig」コーデックを使用して、マークが存在する場合は自動的にスキップします。

Unicodeファイル名

現在一般的に使用されているオペレーティングシステムのほとんどは、任意のUnicode文字を含むファイル名をサポートしています。 通常、これはUnicode文字列をシステムによって異なるエンコーディングに変換することによって実装されます。 今日、PythonはUTF-8の使用に集中しています。MacOS上のPythonはいくつかのバージョンでUTF-8を使用しており、Python3.6はWindowsでもUTF-8を使用するように切り替えています。 Unixシステムでは、LANGまたはLC_CTYPE環境変数を設定した場合にのみファイルシステムエンコーディングが行われます。 そうでない場合、デフォルトのエンコーディングは再びUTF-8です。

sys.getfilesystemencoding()関数は、手動でエンコードを実行する場合に備えて、現在のシステムで使用するエンコードを返しますが、気にする理由はあまりありません。 読み取りまたは書き込み用にファイルを開くときは、通常、ファイル名としてUnicode文字列を指定するだけで、適切なエンコーディングに自動的に変換されます。

filename = 'filename\u4500abc'
with open(filename, 'w') as f:
    f.write('blah\n')

os.stat()などの os モジュールの関数も、Unicodeファイル名を受け入れます。

os.listdir()関数はファイル名を返すため、問題が発生します。Unicodeバージョンのファイル名を返す必要がありますか、それともエンコードされたバージョンを含むバイトを返す必要がありますか? os.listdir()は、ディレクトリパスをバイトとして指定したか、Unicode文字列として指定したかに応じて、両方を実行できます。 Unicode文字列をパスとして渡すと、ファイル名はファイルシステムのエンコーディングを使用してデコードされ、Unicode文字列のリストが返されます。一方、バイトパスを渡すと、ファイル名はバイトとして返されます。 たとえば、デフォルトのファイルシステムエンコーディングがUTF-8であると仮定して、次のプログラムを実行します。

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

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

次の出力が生成されます。

$ python listdir-test.py
[b'filename\xe4\x94\x80abc', ...]
['filename\u4500abc', ...]

最初のリストにはUTF-8でエンコードされたファイル名が含まれ、2番目のリストにはUnicodeバージョンが含まれています。

ほとんどの場合、これらのAPIでUnicodeを使用することに固執する必要があることに注意してください。 バイトAPIは、デコードできないファイル名が存在する可能性があるシステムでのみ使用する必要があります。 それは今ではほとんどUnixシステムだけです。


Unicode対応プログラムを作成するためのヒント

このセクションでは、Unicodeを扱うソフトウェアの作成に関するいくつかの提案を提供します。

最も重要なヒントは次のとおりです。

ソフトウェアは内部でUnicode文字列のみを処理し、入力データをできるだけ早くデコードし、出力を最後にのみエンコードする必要があります。


Unicode文字列とバイト文字列の両方を受け入れる処理関数を作成しようとすると、2つの異なる種類の文字列を組み合わせると、プログラムがバグに対して脆弱になります。 自動エンコードまたはデコードはありません。 str + bytesTypeError が発生します。

Webブラウザーまたはその他の信頼できないソースからのデータを使用する場合、一般的な手法は、生成されたコマンドラインで文字列を使用したりデータベースに保存したりする前に、文字列内の不正な文字をチェックすることです。 これを行う場合は、エンコードされたバイトデータではなく、デコードされた文字列をチェックするように注意してください。 一部のエンコーディングには、全単射ではない、完全にASCII互換ではないなど、興味深い特性がある場合があります。 これは、入力データがエンコードも指定している場合に特に当てはまります。攻撃者は、エンコードされたバイトストリーム内の悪意のあるテキストを隠すための巧妙な方法を選択できるためです。

ファイルエンコーディング間の変換

StreamRecoder クラスは、エンコーディング間で透過的に変換でき、エンコーディング#1でデータを返すストリームを取得し、エンコーディング#2でデータを返すストリームのように動作します。

たとえば、Latin-1の入力ファイル f がある場合、それを StreamRecoder でラップして、UTF-8でエンコードされたバイトを返すことができます。

new_f = codecs.StreamRecoder(f,
    # en/decoder: used by read() to encode its results and
    # by write() to decode its input.
    codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),

    # reader/writer: used to read and write to the stream.
    codecs.getreader('latin-1'), codecs.getwriter('latin-1') )

不明なエンコーディングのファイル

ファイルに変更を加える必要があるが、ファイルのエンコーディングがわからない場合はどうすればよいですか? エンコーディングがASCII互換であることがわかっていて、ASCII部分のみを調べたり変更したりする場合は、surrogateescapeエラーハンドラーを使用してファイルを開くことができます。

with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
    data = f.read()

# make changes to the string 'data'

with open(fname + '.new', 'w',
          encoding="ascii", errors="surrogateescape") as f:
    f.write(data)

surrogateescapeエラーハンドラーは、非ASCIIバイトをU + DC80からU + DCFFまでの特別な範囲のコードポイントとしてデコードします。 surrogateescapeエラーハンドラーを使用してデータをエンコードして書き戻すと、これらのコードポイントは同じバイトに戻ります。


参考文献

Mastering Python 3 Input / Output の1つのセクション、DavidBeazleyによるPyCon2010の講演では、テキスト処理とバイナリデータ処理について説明しています。

Marc-AndréLemburgのプレゼンテーション「PythonでのUnicode対応アプリケーションの作成」 PDFスライドでは、文字エンコードの問題と、アプリケーションを国際化およびローカライズする方法について説明しています。 これらのスライドはPython2.xのみをカバーしています。

PythonのUnicodeの根性は、Python3.3の内部Unicode表現について説明するBenjaminPetersonによるPyCon2013の講演です。


謝辞

このドキュメントの最初のドラフトはAndrewKuchlingによって書かれました。 その後、Alexander Belopolsky、Georg Brandl、Andrew Kuchling、およびEzioMelottiによってさらに改訂されました。

この記事でエラーを指摘したり提案を提供した次の人々に感謝します:ÉricAraujo、Nicholas Bastin、Nick Coghlan、Marius Gedminas、Kent Johnson、Ken Krugler、Marc-AndréLemburg、MartinvonLöwis、TerryJ。 Reedy、Serhiy Storchaka、Eryk Sun、Chad Whitacre、Graham Wideman