Litmus — 話題のニュースを、深く読み解く。 RSS
ソフトウェア

Python 3.15、文字コードの既定がOSを問わずUTF-8に統一

Python 3.15から、encodingを省略したファイル操作はOSに関係なく常にUTF-8になった。Windowsの`open()`だけ取り残されていた経緯も確認した。

暗い背景のモニターに映る、色分けされたPythonふうのプログラムコードのクローズアップ(イメージ写真)

イメージ写真 記事の内容を撮影したものではありません。Photo by Nemuel Sereti on Pexels

この記事には Amazon アソシエイトのリンクを含みます。

Windows機で書いたPythonのスクリプトを、Macを使う同僚にそのまま渡したら、向こうではUnicodeDecodeErrorで止まった。あるいは、同じファイルなのに開くと文字が化けていた。open()という同じ1行のコードなのに、動かすOSによって結果が変わる。この現象は、2026年10月9日にリリースされたPython 3.15で起きにくくなった。encodingを指定しないファイル操作は、OSに関係なく常にUTF-8を使うようになったからだ。これまでOSごとに何が違っていて、Python 3.15は何をどう変えたのか。

同じコードで結果が変わる理由

ロケールが既定値を決めていた

open("file.txt")のようにencoding引数を省略すると、Pythonは「既定のエンコーディング」を使ってファイルを読み書きする。

この既定値は、これまでOSの「ロケール」の設定に従っていた。ロケールとは、OSが地域や言語に応じて持っている設定のことで、日付の表示形式や通貨記号などと一緒に、文字コードの既定値もここに含まれる。

問題は、このロケールの扱いがOSごとに違うことだった。公式のPEP 540によると、ロケール名の付け方はLinuxディストリビューション・macOS・FreeBSDで異なる。C.UTF-8のようなロケールに対応していない環境もあるという(PEP 540)。同じマシンでもSSH接続の設定次第でロケールが変わることもある。

Windowsはさらに別の仕組みを使っていた。PEP 540によれば、WindowsにはPOSIXロケールが存在しない。代わりに「ANSIコードページ」という、OSの地域設定で決まる文字コード番号が、ロケールのエンコーディングとして扱われていた。日本語版Windowsなら、Shift-JIS系の文字コード(コードページ932)がこれにあたる。つまり同じPythonコードでも、open()が解釈に使う文字コードはOSによって別だった。Macでは多くの場合UTF-8、日本語版Windowsではcp932になる。

OSごとの既定値を比べる

Python 3.14以前と3.15以降で、encodingを省略したときの既定値がどう変わったかを整理すると、次のようになる。

環境Python 3.14以前Python 3.15以降
WindowsANSIコードページ(日本語版ならcp932など)UTF-8
macOS / 多くのLinuxロケールの設定値(多くはUTF-8)UTF-8
ロケール未設定・C/POSIXロケールASCII相当になりやすいUTF-8

表から分かるのは、変化の主な影響先がWindowsだということだ。macOSや主要なLinuxディストリビューションはすでにUTF-8ロケールが一般的なため、挙動はほとんど変わらないとPEP 686は述べている。では、Windowsのopen()はなぜこれまでANSIコードページのまま残っていたのか。

Windowsのopenだけ例外だった

具体的に何が起きていたか

日本語版Windowsで、encodingを指定せずに日本語を含むテキストファイルを書き出したとする。そのファイルをMacへ持っていき、同じくencodingを指定せずに開く。Python 3.14までなら、Windows側はcp932で書き、Mac側はUTF-8で読もうとする。この食い違いが、文字化けやUnicodeDecodeErrorの原因になっていた。

PEP 529との非対称性

Python 3.6(2016年)の時点で、PEP 529によってWindowsの多くのAPIはすでにUTF-8化されていた。PEP 540は当時の内訳を具体的に示している。os.fsdecode()やos.fsencode()はUTF-8/surrogatepassになっていた。標準入力はUTF-8/surrogateescapeだった。ところがopen()だけは例外のまま残り、既定はmbcs、つまりANSIコードページだった(PEP 540)。

自分は、Windowsの文字コードはもうUTF-8に揃ったのだと思っていた。ところが、最も基本的なopen()という入り口だけが取り残されていた。

この非対称性を解消する手段として、Python 3.7(2018年)でPEP 540の「UTF-8モード」が導入された。-X utf8オプションか環境変数PYTHONUTF8=1を指定すると、ロケールを無視して常にUTF-8を使うようになる仕組みだ。ただしこの時点ではオプトイン、つまり自分から有効にしないと効かない機能だった。open()がWindowsの標準の文字コードから外れたまま、UTF-8を使うための回避策だけが用意された状態が、ここから8年続いたことになる。

Python 3.15で何が変わったのか

UTF-8モードが既定になった

Python 3.15は、このUTF-8モードを既定で有効にした。根拠となるのがPEP 686だ。UTF-8は事実上の標準的な文字コードであり、JSON・TOML・YAML、多くのエディタ、インターネット上のほぼすべてのWebページがUTF-8を使っていることを理由に挙げている。Node.js・Go・Rust・Javaなど、他の主要な言語もすでにUTF-8を既定にしているとも述べている。

公式ドキュメントの該当箇所を見ると、変更点は次のように説明されている。

Python now uses UTF-8 as the default encoding, independent of the system’s environment. This means that I/O operations without an explicit encoding, for example, open('flying-circus.txt'), will use UTF-8.

(What’s New in Python 3.15より)

つまりencoding引数を書かずにopen()を呼んだときだけ、既定値がUTF-8になる。これまでロケールの違いを気にしてテストしていたコードも、3.15からはOSをまたいで同じ結果になりやすい。

ロケールを使いたいときの逃げ道

どうしてもロケールの文字コードを使いたい場合のための逃げ道も残っている。encoding="locale"と明示すれば、Python 3.10以降、UTF-8モードが有効でもロケールのエンコーディングを使える。また、ロケールの文字コードそのものを取得するlocale.getencoding()という関数も用意されている。これはUTF-8モードの影響を受けず、Python 3.11で追加済みだ。旧来の挙動に完全に戻したい場合は、環境変数PYTHONUTF8=0かコマンドラインオプション-X utf8=0を指定する。

自分のコードは影響を受けるか

影響が出やすいのは、Windows上で動かしていて、かつencodingを指定せずに非ASCII文字を含むファイルを読み書きしているコードだ。PEP 686はこの点について、バージョン間の互換性を保つならencoding引数を常に明示することを推奨している。

具体的な移行手順として、PEP 686は次の4段階を挙げている。

  1. 自分の環境でUTF-8モードを一度無効化する(PYTHONUTF8=0)
  2. EncodingWarning(オプトインの警告)を使って、既定のエンコーディングに依存している箇所を洗い出す
  3. 見つかった箇所にencoding="utf-8"かencoding="locale"を明示する。locale.getpreferredencoding()を使っている箇所はlocale.getencoding()に置き換える
  4. UTF-8モードを有効にした状態であらためてテストする

encodingを省略した箇所が心当たりにない人でも、この手順自体はWindows環境でPythonのファイル操作を書くときのチェックリストとして使える。

まとめ

Python 3.15は、encodingを省略したファイル操作の既定値を、OSに関係なく常にUTF-8にした。これまではOSのロケール設定に従っていた。特にWindowsのopen()は、他の多くのAPIが2016年のPEP 529でUTF-8化されたあとも、ANSIコードページ(日本語版ならcp932など)を既定にし続けていた。2018年のPython 3.7でオプトインの「UTF-8モード」が用意されたものの、既定で有効になるまでにはさらに8年を要している。

影響が出るのは、主にWindows上でencodingを指定せず非ASCII文字を扱っているコードだ。バージョン間の互換性を保ちたい場合は、encoding="utf-8"またはencoding="locale"を明示する。影響の洗い出しにはEncodingWarningが使える。

この記事の先を読む本を探す

特定の本を勧めるものではありません。検索結果から、いまの版と目次を確かめて選んでください。

コメント

気づきや感想をどうぞ

記事への補足や、読んで考えたことをお寄せください。個人情報や、他の人を傷つける内容の投稿はお控えください。

コメントを読み込んでいます…

    ほかの記事

    最新の記事 →