Python 3.15を3.14と比べて検証|Shift_JIS環境で変わるopen()の既定の文字コードと、起動を速くする遅延import

Python 3.15で既定の文字コードがUTF-8に変わり、Shift_JIS系の環境に影響する

Pythonの開発チームは2026年10月9日にPython 3.15.0を公開し、10月25日にはセキュリティ修正を含む3.15.2を出しました。主な変更は、起動を速くする遅延import、変更できない辞書 frozendict などです。

日本語版WindowsなどShift_JIS系の環境で最初に確かめたいのは、ファイルを読み書きするときの既定の文字コードがUTF-8に変わった点です。open() に encoding を書いていないコードは、これらの環境では読み書きする文字コードが変わります。

この記事では、3.14.10と3.15.2を同じMacで並べて、文字コードの変化、遅延importの起動時間、新しい書き方、新しいプロファイラを確かめた結果を紹介します。読み終えると、3.15へ上げる前に自分のコードのどこを直すかを決められます。

3分で把握

encodingを書いていないopen()を探してから、3.15へ上げる

Python 3.14.10と3.15.2を、macOS 27上で比べました。

Shift_JISの環境のopen()

SJIS→utf-8

3.14で書いた日本語入りのファイルは、3.15の既定のままでは UnicodeDecodeError で読めませんでした。

遅延importの起動時間

221→11.5ミリ秒

3.15の通常のimportとlazy importの比較です。9個のモジュールをimportして使わないスクリプトでの中央値で、3.15へ上げるだけでは速くなりません。

3.14でも同じコードを動かす

__lazy_modules__

この変数なら3.14でも構文エラーにならず、3.15でだけ遅延します。

Shift_JISの環境ではopen()の既定がUTF-8に変わった

これまでのPythonは、OSや環境の設定(ロケール)に合わせて既定の文字コードを決めていました。3.15は環境にかかわらずUTF-8を既定にします。「UTF-8モード」と呼ばれる動作を標準で有効にした形です。Windowsの文書によるとOS側のコードページは残りますが、日本語版Windowsでこれまで open() の既定だったcp932も、この変更で使われなくなります。

手元ではWindowsの代わりに、Macの ja_JP.SJIS ロケールで比べました。この環境で実行すると、3.14と3.15で次の違いが出ました。

確かめた項目Python 3.14.10Python 3.15.2
open() の既定SJISutf-8
Path.write_text() で「あ」を書いたバイト列82 a0e3 81 82
io.TextIOWrapper の既定SJISutf-8
sys.stdout の文字コードshift_jisutf-8
UTF-8モード(sys.flags.utf8_mode)01
locale.getencoding()SJISSJIS(変更なし)

macOS 27、ロケール ja_JP.SJISで2026年11月4日に確認。Windowsのコンソールへの出力は3.15より前からUTF-8なので、sys.stdoutの行はWindowsのコンソールには当てはまりません。

影響が出るのは、encoding を書かずにファイルや子プロセスの出力を読み書きしているコードと、Shift_JISを前提に標準出力を受け取るプログラムです。3.14がこの環境で書いた日本語入りのファイルを、3.15で同じコードのまま読むとエラーになりました。

$ LC_ALL=ja_JP.SJIS python3.14 -c "open('memo.txt','w').write('日本語のメモ')"
$ LC_ALL=ja_JP.SJIS python3.15 -c "print(open('memo.txt').read())"
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x93 in position 0: invalid start byte

読み込みの時点で例外になれば、原因は見つけやすいです。注意したいのは逆向きで、例外が出ない場合があります。3.15が書いた「東京都」を3.14で読むと、エラーにならず「譚ア莠ャ驛ス」と文字化けしたまま読めました。3.14が書いたログに3.15が追記した場合もエラーは出ず、2つの文字コードが混ざって、どちらの版でも読めないファイルになりました。

ファイルのほかに、子プロセスの出力も変わります。手元の環境でcp932を出力するコマンドを subprocess.run(text=True) で受けると、3.14は読めて、3.15は UnicodeDecodeError でした。encoding='cp932' を指定すると3.15でも読めます。パイプやファイルへの print() の出力もUTF-8に変わります。受け取る側がShift_JISを前提にしているなら、そちらの確認も必要です。

ロケールがUTF-8の環境では、open() の既定は3.14でも3.15でもUTF-8で、読み書きの結果は同じでした。ロケールがUTF-8のMacやLinuxでは、この変更で困る場面はほとんどありません。

読み書きするファイルに合わせて3つの直し方を選ぶ

文字コードが決まっているファイル

encoding='cp932' や encoding='utf-8' と書きます。どの版、どの環境でも同じように動くので、まず選びたい方法です。

OSの設定に合わせたいファイル

encoding='locale' と書きます。手元の ja_JP.SJIS の環境で、3.14が書いたファイルを正しく読めました。

すぐにコードを直せない場合

PYTHONUTF8=0 か -X utf8=0 で以前の動作に戻します。手元でも正しく読めました。直すまでのつなぎとして使います。

直す場所は、Pythonの警告で探せます。-X warn_default_encoding を付けて実行すると、実行された open() のうち encoding のない行に EncodingWarning が出ました。3.14でも使えるので、更新の前に今のコードで確かめられます。print() の出力先の変化はこの警告では見つからないので、Shift_JISのまま受け取るプログラムは別に確かめます。

$ python3.14 -X warn_default_encoding app.py
app.py:1: EncodingWarning: 'encoding' argument not specified

使わないモジュールなら遅延importで起動時間が大きく縮む

3.15の新しい構文 lazy import は、モジュールの読み込みを最初に使う瞬間まで遅らせます。これまでは関数の中へ import を移して同じことをしていましたが、ファイルの先頭にまとめて書いたまま遅らせられます。

効果を見るため、asyncio や ssl など9個の標準モジュールをimportし、使わずに終わるスクリプトの実行時間を31回ずつ測りました。

9個のモジュールをimportして使わないスクリプトの実行時間(中央値)

3.14:通常のimport:212.9ミリ秒

3.15:通常のimport:221.3ミリ秒

3.15:lazy import:11.5ミリ秒

3.15:__lazy_modules__:11.5ミリ秒

3.15:通常のimportに -X lazy_imports=all:11.5ミリ秒

何も書かない空のスクリプトは、どちらの版も11.5ミリ秒でした。macOS 27、Apple Silicon、31回実行の中央値。使わないモジュールだけの例なので、使えば読み込みの時間はその時点でかかります。

-X lazy_imports=all は、コードを書き換えずに通常の import をまとめて遅延させる起動オプションです。読み込みの失敗の時期も変わるので、効果を測る用途に向きます。コードに書く場合の形は次のとおりです。3.14では lazy の行が構文エラーになるので、両方の版で動かすライブラリには __lazy_modules__ が向いています。3.14はこの変数を普通の変数として無視し、通常どおり読み込みました。

lazy import json              # 3.15のみ。3.14では SyntaxError

__lazy_modules__ = ['json']   # 3.14でも動く。遅延するのは3.15だけ
import json

読み込みの失敗が表に出る時期も変わります。存在しないモジュールを lazy import しても、その行は通り、最初に使う行で ModuleNotFoundError になりました。起動時に必ず確かめたい依存関係は、通常の import のまま残します。手元では、関数の中、try の中、from … import * に lazy を付けると SyntaxError になりました。__lazy_modules__ は対象の import より前に書きます。

frozendictなど3つの新しい書き方は短いコードで試せる

構文と組み込み型の追加は、次の3つを両方の版で試しました。いずれも3.15で動き、3.14では NameError か SyntaxError になります。結果は行ごとにコメントで示しました。

# 変更できない辞書。辞書のキーにも使える
d = frozendict(a=1, b=2)
{d: 'key'}[frozendict(b=2, a=1)]   # 'key'(順序は比較に影響しない)
d['a'] = 9                         # TypeError

# 「引数なし」を None と区別するための値
MISSING = sentinel('MISSING')
def f(x=MISSING):
    return 'なし' if x is MISSING else x
f(), f(None)                       # ('なし', None)

# 内包表記の中で展開する
[*x for x in [[1, 2], [3]]]        # [1, 2, 3]
{**d for d in [{'a': 1}, {'b': 2}]}  # {'a': 1, 'b': 2}

設定値のように書き換えられたくない辞書には frozendict、「指定なし」の目印を自作していた箇所には sentinel が使えます。どちらも3.14では動かないので、3.14をサポートするライブラリでは最低バージョンを上げるまで待つことになります。

新しいプロファイラTachyonはMacでは管理者権限が要る

3.15には、動いているプロセスの処理を一定間隔で記録するプロファイラ profiling.sampling(Tachyon)が入りました。速度のボトルネックを探す道具で、コードを変更せず、動作中のプロセスにも使えると説明されています。

手元のMacで python3.15 -m profiling.sampling run を実行すると、プロセスのメモリを読む権限がないというエラーで止まり、sudo での再実行を案内されました。公式のDockerイメージ python:3.15-slim では、追加の設定なしで動き、関数ごとの時間を表示しました。コンテナの中で動かすコードを測るなら、Dockerから始めると管理者権限を使わずに済みます。

$ docker run --rm -v "$PWD":/w python:3.15-slim \
    python -m profiling.sampling run --pstats /w/prof.py
Captured 793 samples in 0.79 seconds
Sample rate: 999.37 samples/sec
Error rate: 23.58
  nsamples   sample%  tottime (ms)  ...  filename:lineno(function)
   416/416      68.6       416.000  ...  prof.py:3(slow.<locals>.<genexpr>)
   190/606      31.4       190.000  ...  prof.py:3(slow)

表の prof.py は、重い計算を繰り返す短い試験用のスクリプトです。Error rate は読み取りに失敗した標本の割合で、手元の実行では2〜3割でした。そのぶん、表の標本の合計は取得数より少なくなっています。

3.15へ上げる前に確かめる4つのこと

  1. 今の版で -X warn_default_encoding を付けてテストを実行し、encoding のない読み書きを洗い出す。
  2. 今の版が書いた日本語入りのファイルを3.15で読むテストを足し、Windowsを使う人がいるならWindowsでも実行する。
  3. 公式の変更点の「Removed」を読み、使っている機能が消えていないかを確かめる。たとえば http.server の CGIHTTPRequestHandler は、手元でも ImportError になりました。
  4. 起動の遅いコマンドラインツールがあれば、-X lazy_imports=all で起動時間を測り、効果を見てから遅らせるモジュールを lazy で指定する。

私なら、3.15へ上げるかどうかにかかわらず、まず1番目の警告だけを今のコードで試します。encoding を明示する修正は3.14でもそのまま役に立ち、更新のときの失敗も減らせます。

参考資料

  • What's new in Python 3.15(Python公式ドキュメント、2026年11月4日に閲覧)。UTF-8の既定、遅延import、frozendict、sentinel、内包表記、Tachyon、削除された機能
  • Using Python on Windows(Python公式ドキュメント、2026年11月4日に閲覧)。WindowsのUTF-8モードとコードページ
  • Python 3.15.0(python.org、2026年10月9日)。正式版の公開
  • Python 3.15.2(python.org、2026年10月25日)。検証に使った版
  • PEP 686、PEP 810(Python Enhancement Proposals)。UTF-8の既定と遅延importの提案