環境
- Windows11 Pro(22H2)
- コマンドプロンプト
- Python3.11.2
概要
WindowsでPythonスクリプトを実行しています。
Pythonスクリプトでは、日本語を出力しています。
システムロケールが英語のとき、以下のコードを実行するとUnicodeEncodeError
が発生しました。
> python -c "print('あ')"
あ
> python -c "print('あ')" > out.txt
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\WDAGUtilityAccount\AppData\Local\Programs\Python\Python311\Lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeEncodeError: 'charmap' codec can't encode character '\u3042' in position 0: character maps to <undefined>
その原因についてまとめた記事です。
※原因調査は同僚がやってくれました。私は同僚が調査してくれた内容を、自分の理解できる範囲でブログに書いただけです。
システムロケールについて
公式サイトには以下のように記載されています。
SystemLocale は、Unicode 対応でないプログラムで使用する既定の言語を指定します。
この設定は、Windows セットアップと Windows 展開サービスの両方で使用されます。
システム ロケールは、既定でシステムが使用するビットマップ フォントとコード ページ (ANSI または DOS) を指定します。 システム ロケール設定は、ANSI (Unicode 対応でない) アプリケーションのみに影響します。 Unicode 対応でないプログラムの言語は、システムごとの設定です。
ユーザーは、コントロール パネルの地域と言語の項目で [管理] タブを使用して、システム ロケールを変更できます。
システムロケールはsysteminfo
コマンドで確認できます。
> systeminfo
...
システム ロケール: ja;日本語
入力ロケール: ja;日本語
...
システムロケールによって、コードページ(≒文字コード?)が決まります。
コードページはchcp
コマンドで確認できます。
またPythonでは、locale.getencoding(Python3.11から利用可能)で現在のコードページを確認できます。
Get the current locale encoding:
On Android and VxWorks, return "utf-8".
On Unix, return the encoding of the current LC_CTYPE locale. Return "utf-8" if nl_langinfo(CODESET) returns an empty string: for example, if the current LC_CTYPE locale is not supported.
On Windows, return the ANSI code page.
システムロケールが日本語(ja;日本語)の場合
コードページはcp932
>chcp
現在のコード ページ: 932
>python -c "import locale; print(locale.getencoding())"
cp932
システムロケールが英語(en-us;English (United States))の場合
コードページはcp437
>chcp
Active code page: 437
>python -c "import locale; print(locale.getencoding())"
cp1252
※ Pythonのlocale.getencoding()
はcp1252
を返しました。cp437とcp1252が同じなのかがどうか、私には分かりませんでした。。。
ファイルへリダイレクトしたときの動きを確認する
ファイルへリダイレクトしたときの動きを、システムロケールが日本語と英語の場合で確認しました。結果は以下の通りです。
システムロケール | リダイレクトの元 | 結果 |
---|---|---|
日本語 | 標準出力 | OK |
日本語 | 標準エラー出力 | OK |
英語 | 標準出力 | UnicodeEncodeErrorが発生 |
英語 | 標準エラー出力 |
\u 形式でファイルに出力される |
システムロケールが日本語の場合
標準出力をファイルへリダイレクト
> python -c "print('あ')" > out.txt
> more out.txt
あ
標準エラー出力をファイルへリダイレクト
> python -c "import sys; print('あ', file=sys.stderr)" 2> out.txt
> more out.txt
あ
システムロケールが英語の場合
標準出力をファイルへリダイレクト
> python -c "print('あ')" > out.txt
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\WDAGUtilityAccount\AppData\Local\Programs\Python\Python311\Lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeEncodeError: 'charmap' codec can't encode character '\u3042' in position 0: character maps to <undefined>
標準エラー出力をファイルへリダイレクト
> python -c "import sys; print('あ', file=sys.stderr)" 2> out.txt
> more out.txt
\u3042
sys.stdout
, sys.stderr
の中身を確認する
以下のpythonファイルで、sys.stdout
, sys.stderr
の中身を確認しました。
import sys
print("=== sys.stdout ===")
f = sys.stdout
print(f"{f.buffer.raw=}, {f.isatty()=}, {f.encoding=}, {f.errors=}")
print("=== sys.stderr ===")
f = sys.stderr
print(f"{f.buffer.raw=}, {f.isatty()=}, {f.encoding=}, {f.errors=}")
-
isatty()
: ストリームが対話的か -
errors
: エンコード/デコードのエラーをどのように扱うか
システムロケールが日本語の場合
> python sample.py
=== sys.stdout ===
f.buffer.raw=<_io._WindowsConsoleIO mode='wb' closefd=False>, f.isatty()=True, f.encoding='utf-8', f.errors='surrogateescape'
=== sys.stderr ===
f.buffer.raw=<_io._WindowsConsoleIO mode='wb' closefd=False>, f.isatty()=True, f.encoding='utf-8', f.errors='backslashreplace'
> python sample.py > out.txt 2>&1
> more out.txt
=== sys.stdout ===
f.buffer.raw=<_io.FileIO name='<stdout>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='cp932', f.errors='surrogateescape'
=== sys.stderr ===
f.buffer.raw=<_io.FileIO name='<stderr>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='cp932', f.errors='backslashreplace'
システムロケールが英語の場合
> python sample.py
=== sys.stdout ===
f.buffer.raw=<_io._WindowsConsoleIO mode='wb' closefd=False>, f.isatty()=True, f.encoding='utf-8', f.errors='surrogateescape'
=== sys.stderr ===
f.buffer.raw=<_io._WindowsConsoleIO mode='wb' closefd=False>, f.isatty()=True, f.encoding='utf-8', f.errors='backslashreplace'
> python sample.py > out.txt 2>&1
> more out.txt
=== sys.stdout ===
f.buffer.raw=<_io.FileIO name='<stdout>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='cp1252', f.errors='surrogateescape'
=== sys.stderr ===
f.buffer.raw=<_io.FileIO name='<stderr>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='cp1252', f.errors='backslashreplace'
起きたことの整理
システムロケールが英語のとき、ファイルへリダイレクトすると期待する結果にならない理由
ファイルへリダイレクトしない場合は、システムロケールに関わらず標準出力/標準エラー出力のencoding
はutf-8
です。
しかし、ファイルへリダイレクトするとencoding
がシステムロケールによって決まるコードページになります。
あ
という文字はcp1252
で表現できないので、ファイルへリダイレクトすると期待する結果になりませんでした。
システムロケールが英語のとき、標準出力と標準エラー出力で結果が異なる理由
標準出力と標準エラー出力で、errors
の値が異なるためです。
標準エラー出力のとき、errors
はbackslashreplace
です。backslashreplace
の動きは以下の通りです。
'backslashreplace' を指定すると、不正な形式のデータをバックスラッシュ付きのエスケープシーケンスに置換します。
一方、標準出力のときerrors
はsurrogateescape
です。surrogateescape
は表現できない文字をエンコードしようとするとUnicodeEncodeError
が発生するようです(surrogateescape
の正しい動きを理解できませんでした。。。)
surrogateescape
の動きについては、以下の記事を参照ください。
標準出力と標準エラー出力でerrors
の値が異なる理由は、分かりませんでした。
対策
UTF-8 Modeを利用すれば、ファイルへリダイレクトしたときもencoding
がutf-8
になり、期待する結果になります。
# UTF-8 Modeを有効にする
> set PYTHONUTF8=1
> python -c "print('あ')" > out.txt
>more out.txt
πüé
out.txt
はmore
コマンドでは化けて表示されますが、VSCodeなどのエディタで開ければあ
と表示されます。
また、ファイルへリダイレクトする際もencoding
はutf-8
のままです。
> python sample.py > out.txt 2>&1
>more out.txt
=== sys.stdout ===
f.buffer.raw=<_io.FileIO name='<stdout>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='utf-8', f.errors='surrogateescape'
=== sys.stderr ===
f.buffer.raw=<_io.FileIO name='<stderr>' mode='wb' closefd=False>, f.isatty()=False, f.encoding='utf-8', f.errors='backslashreplace'
まとめ
以下の条件を全て満たすとき、UnicodeEncodeError
が発生しました。
- Windowsでシステムロケールが英語
- Pythonで
あ
という文字を標準出力へ出力する - Pythonコマンドの結果をファイルへリダイレクトする
原因は以下の通りです。
- システムロケールを英語にすると、Pythonでコードページは
437
(cp1252)になる。 -
あ
という文字はcp1252では表現できない。 -
sys.stdout.errors
はsurrogateescape
。surrogateescape
は、表現できない文字をエンコードしようとするとUnicodeEncodeError
が発生する。
補足
PowerShellで標準エラー出力をファイルへリダイレクトすると、NativeCommandError
が出力される
以下のコマンドをPowerShellで実行すると、NativeCommandError
が発生します。
PS > python -c "import sys; print('あ', file=sys.stderr)" 2> out.txt
PS > more out.txt
python : あ
発生場所 行:1 文字:1
+ python -c "import sys; print('あ', file=sys.stderr)" 2> out.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (あ:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PowerShellによるエラーで、今回の問題とは関係ありません。
参考サイト