エラーの内容
WindowsでPythonを使ってテキストファイルを読み込もうとしたとき、突然このエラーが発生することがあります:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 10859: character maps to <undefined>
メモ帳やVS Codeでは問題なく開けるのに、Pythonは拒否します。原因はひとつ:エンコーディングのデフォルト設定です。
根本原因
WindowsでPythonのopen()を使うと、システムロケールのエンコーディング(通常はcp1252、Windows-1252とも呼ばれ、charmapとも称される)がデフォルトになります。これはラテン文字の一部しかカバーしていません。
ファイルにその範囲外のバイト(UTF-8テキスト、別コードページのファイル、絵文字や拡張Unicodeを含むもの)が含まれていると、PythonはUnicodeDecodeErrorをスローします。バイト値0x9dはその典型例で、UTF-8では有効ですがcp1252では未定義です。
macOSやLinuxではほとんど発生しません。それらのデフォルトはすでにUTF-8です。Windowsが例外なのです。
修正方法1:UTF-8エンコーディングを明示する(9割のケースで解決)
open()の呼び出しにencoding='utf-8'を追加するだけです:
# 修正前(Windowsでエラーになる)
with open('data.txt') as f:
content = f.read()
# 修正後(どこでも動作する)
with open('data.txt', encoding='utf-8') as f:
content = f.read()
ファイルがUTF-8で保存されている場合(現代のほとんどのツールのデフォルト)、これだけで済みます。1行の変更です。
動作確認
with open('data.txt', encoding='utf-8') as f:
content = f.read()
print(f"Read {len(content)} characters successfully")
例外は発生しません。修正完了です。
修正方法2:エンコーディングが不明な場合
クライアントやサードパーティシステムからファイルを受け取る場合、エンコーディングはcp1252、latin-1、shift-jis、GBKなど何でもありえます。chardetを使って自動検出しましょう。
pip install chardet
import chardet
# まず生のバイト列として読み込む
with open('data.txt', 'rb') as f:
raw = f.read()
result = chardet.detect(raw)
detected_encoding = result['encoding']
confidence = result['confidence']
print(f"Detected: {detected_encoding} (confidence: {confidence:.0%})")
# 検出したエンコーディングで読み込む
with open('data.txt', encoding=detected_encoding) as f:
content = f.read()
信頼度が70%未満の場合、ファイルが破損しているか、エンコーディングが混在しているか、非常にマイナーなコードページが使われている可能性があります。結果を処理する前に送信者に確認してください。
修正方法3:問題のあるバイトをスキップまたは置換する
ファイルを素早く読み込む必要があり、一部の読めない文字を気にしない場合は、errorsパラメータで対処できます:
# デコードできないバイトを置換文字(U+FFFD)に置き換える
with open('data.txt', encoding='utf-8', errors='replace') as f:
content = f.read()
# または完全に除去する
with open('data.txt', encoding='utf-8', errors='ignore') as f:
content = f.read()
注意点:errors='ignore'はデータを無警告で破棄します。顧客データや財務エクスポートを処理している場合、何が失われたか永遠にわかりません。読めない文字が本当に重要でないケースに限定して使用してください。
修正方法4:プロセス全体でUTF-8を強制する
ファイル操作が散在するスクリプトを実行する場合は、PYTHONUTF8=1を一度設定するだけで、すべてのopen()呼び出しが自動的にUTF-8をデフォルトにします:
# コマンドプロンプト
set PYTHONUTF8=1
python your_script.py
# PowerShell
$env:PYTHONUTF8 = "1"
python your_script.py
または-Xフラグでインライン指定もできます:
python -X utf8 your_script.py
UTF-8モードはプロセス全体(ファイルI/O、stdin、stdout、stderr、すべて)に適用されます。Python 3.7以降で利用可能です。
永続的に設定するには、Windowsの環境変数にPYTHONUTF8=1を追加します:システムのプロパティ → 詳細設定 → 環境変数 → 新規(「ユーザー環境変数」の下)。
修正方法5:古いWindowsファイルにはlatin-1を使う
古いAccessデータベース、レガシーCRM、政府のデータポータルからのエクスポートなど、一部のレガシーファイルは実際にcp1252またはlatin-1です。chardetがこれを確認した場合は、適切なコーデックで読み込みます:
with open('legacy_file.txt', encoding='cp1252') as f:
content = f.read()
# latin-1はバイト値0x00-0xFFすべてをUnicode文字にマッピングする
# そのためUnicodeDecodeErrorが発生しない
with open('mystery_file.txt', encoding='latin-1') as f:
content = f.read()
latin-1はすべてのバイト値をエラーなしで受け入れます。それがフォールバックとして便利な理由です。ただし、ファイルが実際にUTF-8の場合、出力は文字化けします。最後の手段として使い、最初には使わないでください。
システムのデフォルトエンコーディングを確認する
お使いのマシンでPythonが何をデフォルトにしているか確認するには:
import sys
import locale
print(sys.getdefaultencoding()) # 通常は 'utf-8'
print(sys.getfilesystemencoding()) # Mac/Linuxは 'utf-8'、Windowsは 'mbcs'
print(locale.getpreferredencoding()) # open() が実際に使うもの — よく 'cp1252'
locale.getpreferredencoding()がcp1252を返す場合、それが原因です。明示的なencoding=指定のないすべてのopen()呼び出しが、警告なしにそれを使用します。
予防策
- すべての
open()呼び出しに必ずencoding='utf-8'を書く。UTF-8がデフォルトのLinuxやmacOS上でも書くようにしましょう。コードはいつかWindowsで実行されることになります。 - エディタでファイルをUTF-8で保存する。VS CodeはデフォルトでUTF-8です。Windows 10・11のメモ帳もUTF-8がデフォルトになりましたが、古いバージョンはANSI/cp1252がデフォルトでした。
- ライブラリを公開する場合は、ファイルを読み込む関数に
encodingパラメータを受け付けるようにし、デフォルト値をドキュメントに明記してください。 - Pylintの
W1514(unspecified-encoding)は、明示的なエンコーディングのないすべてのopen()にフラグを立てます。CIパイプラインに組み込めば、本番環境に届く前に問題を検出できます。

