【実測】Python 3.15の新機能と変更点まとめ|遅延importで起動が速くなる
Python 3.15は2026年10月リリース予定です。起動を速くする遅延import(lazy import)、文字化けを防ぐUTF-8の標準化、稼働中でも使える新しいプロファイラなど主要な変更点を、実際にコードを動かして解説します。あわせて3.15で使えなくなる古いコードと移行方法も実測でまとめました。

堀川 慎
Backend Engineer / AWS / Django / Go
Python 3.15は2026年10月リリース予定です。起動を速くする遅延import(lazy import)、文字化けを防ぐUTF-8の標準化、稼働中でも使える新しいプロファイラなど主要な変更点を、実際にコードを動かして解説します。あわせて3.15で使えなくなる古いコードと移行方法も実測でまとめました。
3.15の目玉は「起動を速くする遅延import」
Python 3.15が2026年10月1日にリリース予定です。今回もたくさんの変更が入りますが、多くの開発者にとって一番うれしいのは 遅延import(lazy import) だと思います。必要になるまでモジュールを読み込まない仕組みで、これまで「起動が遅い」と言われてきたコマンドラインツールが目に見えて速くなります。
この記事では、まだベータ版の Python 3.15(3.15.0b2)を実際に手元にインストールして、主要な変更点を1つずつ動かしながら確認しました。「何が新しくなるのか」だけでなく、「これまで何が不便で、それがどう良くなるのか」「3.15に上げたときに動かなくなる古いコードはどれか」まで、実行結果を見ながらまとめています。
先に今回の検証結果を一覧にしておきます。
| 変更点 | 何がうれしいか | タイプ |
|---|---|---|
| 遅延import(PEP 810) | 起動が速くなる(検証では約4倍) | できなかったことができる |
| UTF-8が標準(PEP 686) | 文字化けが減る | ベターになる(注意も要る) |
| 新プロファイラ(PEP 799) | 稼働中のプログラムを止めずに性能を測れる | できなかったことができる |
| 内包表記の展開(PEP 798) | リストの平坦化が短く書ける | ベターになる |
| 古いAPIの削除 | (移行時の注意点) | 使えなくなる |
この記事で使った検証コードは、すべてGitHubのパブリックリポジトリに置いてあります。uv python install 3.15 でベータ版を入れて、同じ手順で再現できます。
なお、3.13で「インタプリタより遅い」と言われたJITコンパイラも3.15で巻き返していますが、そちらは別の記事(Python 3.15 JIT、軌道に復帰)で詳しく扱っているので、本記事ではそれ以外の変更を中心に見ていきます。
Python 3.15はいつ出る?今の開発状況
Python 3.15の正式リリースは2026年10月1日の予定です(リリース日程をまとめた PEP 790 より)。Pythonは毎年10月に新しいバージョンを出すサイクルになっていて、3.15もこの流れに沿っています。
2026年6月の時点ではベータ版の段階です。機能を追加する時期(アルファ)は終わっていて、いまは細かい調整とバグ修正のフェーズに入っています。この記事の検証に使ったのは、ベータ2にあたる 3.15.0b2 です。
# uvでベータ版をインストール $ uv python install 3.15 $ python3.15 -VV Python 3.15.0b2 (main, Jun 11 2026, 04:04:48) [Clang 22.1.3]
ベータ版なので、ここから正式版までに細かい挙動が変わる可能性はあります(実際、この記事の後半で「ドキュメントには削除予定と書いてあるのに、ベータ版ではまだ残っている」機能が出てきます)。本番環境にすぐ入れるものではありませんが、「自分のコードが3.15で動くか」を今のうちに試しておくには十分です。
起動が速くなる「遅延import」とは(PEP 810)
今回の一番の目玉です。Pythonでは import をファイルの先頭にまとめて書くのが定石ですが、これだと「使うかどうかわからないモジュール」もプログラム起動時にすべて読み込まれます。コマンドラインツールが --help を表示するだけなのに、裏で何十個ものモジュールを読んでいる、というのはよくある話です。
これまでは、対策として「重い import を関数の中に隠す」という書き方が使われてきました。PEP 810によると、標準ライブラリの import の約17%が、この回避策のために関数の中に散らばっているそうです。3.15では、これを言語の機能として正面から解決します。import の前に lazy を付けるだけです。
# lazy を付けると、json はこの時点ではまだ読み込まれない
lazy import json
print("json in sys.modules before first use:", "json" in sys.modules)
# => False(まだ読み込まれていない)
# 名前を初めて「使った」瞬間に、本物のモジュールが読み込まれる
json.dumps({"hello": "world"})
print("json in sys.modules after first use: ", "json" in sys.modules)
# => True(ここで読み込まれた)実際に動かすと、最初のアクセスまで json が読み込まれていないことがはっきり確認できました。lazy import と書いた行では、本物のモジュールの代わりに「身代わり(プロキシ)」が置かれているだけで、名前を初めて使ったときに本物に入れ替わる仕組みです。
どれくらい速くなるのか実測した
体感ではなく数字で見たいので、重ための標準ライブラリ5つ(json, pathlib, argparse, logging, http.client)を import するだけのスクリプトを用意し、「普通に import した場合」と「lazy import にして使わなかった場合」で、起動から終了までの時間を30回ずつ測りました。
| 書き方 | 起動時間(中央値) | 読み込まれたモジュール数 |
|---|---|---|
| 普通の import | 21.1 ミリ秒 | 122 個 |
| 遅延import | 5.2 ミリ秒 | 26 個 |
起動時間は 約21ミリ秒から約5ミリ秒へ、おおよそ4倍。読み込まれるモジュール数も 122個から26個に減りました。もちろんこれは「import したものを使わなかった」極端な例なので、実際の効果は何を import しているかによります。それでも、起動のたびに毎回読み込んでいた重いライブラリを後回しにできる効果は大きいです。コマンドを叩くたびに一瞬待たされていたツールが、きびきび動くようになります。
関数の中に隠していたimportを卒業できる
速くなるのは確かにうれしいのですが、私が一番ありがたいと思ったのは可読性のほうです。これまで「起動を速くするためだけに、重いimportを関数の中に書く」という回避策がよく使われてきました。先ほど触れたとおり、標準ライブラリでも約17%のimportがこの理由で関数の中に散らばっているそうです。設計上の都合でやむなくそうしていた、という人も多いはずです。
3.15では、こうしたimportをファイルの先頭の lazy import に戻せます。依存しているライブラリが冒頭にきれいに並ぶので、「このファイルが何に依存しているか」が一目でわかります。関数の中のimportのように、呼ぶたびに名前解決のコストがかかることもありません。起動の速さと読みやすさを、両方あきらめずに済みます。
ただし、すべての関数内importが不要になるわけではありません。「OSや実行環境によって必要なときだけ重いライブラリを読む」「循環import(お互いを参照し合うモジュール同士)を避けるためにわざと遅らせている」といった、ロジック上の理由がある場合は、関数の中に置いたままのほうが意図が伝わります(lazy import はトップレベルにしか書けませんし、循環importを自動で解消してくれるわけでもありません)。起動高速化のためだけに散らしていたimportは堂々と先頭へ戻し、理由があるものは残す。この線引きさえ守れば、可読性は確実に上がります。
使うときの注意点
便利な反面、いくつか落とし穴があります。実際に試して分かった点を挙げておきます。
- • 書ける場所が限られる。
lazy importはファイルのトップレベルにだけ書けます。関数の中やtryの中、from x import *のような書き方には使えません。 - • エラーが出るタイミングが後ろにずれる。存在しないモジュールを
lazy importしても、import 行ではエラーになりません。初めて使ったときに初めて失敗します。 - • import した瞬間に何かをするモジュールと相性が悪い。読み込まれた時点で登録処理などをするライブラリは、使うまでその処理が走りません。
なお、lazy import と明示的に書いた分は、特別なオプションなしでもそのまま遅延されます。プログラム全体の挙動をまとめて切り替えたい場合は、-X lazy_imports=all のように起動オプションで指定します。ここで1つ実測でつまずいたのですが、このオプションは単なるオン・オフのフラグではなく、all / none / normal のいずれかの値を必ず指定する必要があります。値を付けずに -X lazy_imports とだけ書くと、起動時にエラーになりました。
文字化けが消える?UTF-8が標準の文字コードに(PEP 686)
3.15では、文字コード(テキストをコンピュータが扱うための符号)の標準が UTF-8 になります。UTF-8は世界中の文字を扱える事実上の標準的な文字コードです。
これまでのPythonは、open() でファイルを開くときに encoding を指定しないと、OSの地域設定(ロケール)に従っていました。日本語版Windowsでは、これがUTF-8ではなく cp932 という古い文字コードになっていて、UTF-8で書かれたファイルを読むと文字化けする、という事故が起きがちでした。3.15では、この既定値がUTF-8に統一されます。
# 3.15 で実行(encoding を指定していない)
import sys
print("sys.flags.utf8_mode:", sys.flags.utf8_mode) # => 1
with open("sample.txt", "w") as f: # encoding= を書いていない
print("default open() encoding:", f.encoding) # => utf-8
f.write("日本語テキスト")3.15では、UTF-8モードを表す sys.flags.utf8_mode が、何も指定しなくても 1(オン) になっていました。3.12では同じ確認をすると 0(オフ) です。
正直に書いておくと、LinuxやmacOSのように元からUTF-8の環境では、体感の差はほとんどありません(もともと open() はUTF-8で開けていました)。この変更が効くのは主にWindowsです。「encodingを指定し忘れたら文字化けした」というあの定番のトラブルが、デフォルトで起きにくくなる、というのが本質です。
逆に、古い文字コードのファイルを前提にしていたコードは、3.15で挙動が変わる可能性があります。元の「ロケールに従う」挙動に戻したいときは、環境変数 PYTHONUTF8=0 を設定するか、起動時に -X utf8=0 を付けます。個別のファイルだけ元の挙動にしたいときは open(..., encoding="locale") と書けます。
稼働中でも性能を測れる新しいプロファイラ(PEP 799)
プロファイラとは「プログラムのどこで時間がかかっているか」を測る道具です。3.15には profiling.sampling という新しいプロファイラが標準で入りました。一定間隔で「今どこを実行しているか」をサンプリングする方式で、プログラムをほとんど遅くせずに測れるのが特長です。
これまでの cProfile は、測りたいコードをあらかじめ仕込んでおく必要がありました。新しいプロファイラは、すでに動いているプログラムに後から接続して測れるのが大きな違いです。「本番でなぜか重いプロセス」に、プログラムを止めずにプロセス番号を指定して接続できます。
# スクリプトを実行しながら測り、インタラクティブなflamegraphを出力
$ python3.15 -m profiling.sampling run -r 10khz \
--flamegraph -o flamegraph.html workload.py
# すでに動いているプロセス(PID 12345)に後から接続して測る
$ python3.15 -m profiling.sampling attach 12345 --mode cpu試しに、フィボナッチ数列を計算する重めのスクリプトを毎秒1万回のペースでサンプリングしてみました。結果は、処理時間の 83.7% が再帰的なフィボナッチ計算に費やされている、と一目で分かる形で出ました。下のflamegraph(炎のグラフ。横幅がそのまま「そこで使った時間の割合」を表します)は、実際に出力されたものをそのまま埋め込んでいます。バーをクリックすると掘り下げられます。
↑ Python 3.15の profiling.sampling が生成したflamegraph(別タブで開く)
出力形式は他にも、従来の cProfile 風の表(--pstats)、行ごとの負荷を色で示すヒートマップ(--heatmap)、ターミナル上でリアルタイムに見る表示(--live)などが選べます。「全体時間」だけでなく「CPUを使っていた時間だけ」「GILを持っていた時間だけ」といった測り方も指定できます。なお、これまでの cProfile 系は profiling.tracing という名前に整理されました。
地味に効く新機能(内包表記の展開・新しい型・親切なエラー)
内包表記の中で展開できる(PEP 798)
リストの中にリストが入っている入れ子構造を「1段ならす(平坦化する)」とき、これまでは for を2回書く必要がありました。3.15では * を使って素直に書けます。
lists = [[1, 2], [3, 4], [5]] # これまで(forを2回) old = [x for sub in lists for x in sub] # 3.15(* で展開) new = [*sub for sub in lists] print(new) # => [1, 2, 3, 4, 5]
辞書をまとめる {**d for d in dicts} のような書き方もできます。劇的な変化ではありませんが、「あの入れ子のfor、毎回読みづらかった」という人には地味にうれしい改善です。
変更できない辞書と「目印」の値が組み込みに
中身を変更できない辞書 frozendict(PEP 814)と、「値がない」ことを表す目印を作る sentinel(PEP 661)が、追加のインストールなしで使えるようになりました。
# 変更できない辞書
config = frozendict(host="localhost", port=8080)
config["port"] = 9090
# => TypeError: 'frozendict' object does not support item assignment
# 「値が渡されなかった」を None と区別して表せる目印
MISSING = sentinel("MISSING")
print(MISSING) # => MISSINGfrozendict は変更できないので、辞書のキーにしたり、書き換えられたくない設定として安全に渡せます。sentinel は「引数が省略された」のか「None が明示的に渡された」のかを区別したいときに便利で、これまで各自が自前で用意していたものが標準になりました。
エラーメッセージが他言語経験者にやさしくなった
他の言語から来た人がやりがちな間違いに対して、正しいメソッド名を提案してくれるようになりました。実際に3.15で動かすと、こう出ます。
>>> [1, 2, 3].push(4)
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?
>>> "hello".toUpperCase()
AttributeError: 'str' object has no attribute 'toUpperCase'. Did you mean '.upper'?
>>> {}.put("a", 1)
AttributeError: 'dict' object has no attribute 'put'. Use d[k] = v.JavaScriptの .push() やJavaの .put() を、Pythonの正しい書き方にそっと案内してくれます。1つ実測でのメモを残しておくと、この提案文はエラーを画面に表示するときに付くもので、except で捕まえて str(例外) をそのまま出しても提案は出ません(traceback モジュール経由で整形すると出ます)。
3.15で使えなくなる古いコード(移行時の注意点)
古いバージョンから3.15へ一気に上げると、これまで動いていたコードが動かなくなることがあります。長く非推奨だった機能が、3.15でいくつか正式に削除されました。同じスクリプトを3.12と3.15で実行して、何が消えたかを実際に確認しました。
| 機能 | 3.12 | 3.15 | 代わりに使うもの |
|---|---|---|---|
| sre_compile / sre_parse / sre_constants | 使える | 削除 | re モジュール |
| pathlib.PurePath.is_reserved() | 使える | 削除 | os.path.isreserved() |
| http.server.CGIHTTPRequestHandler | 使える | 削除 | 専用のWSGI/ASGIサーバー |
| types.CodeType.co_lnotab | 使える | 削除 | co_lines() |
| locale.getdefaultlocale() | 使える(警告) | まだ残っていた | getencoding() 等 |
実行結果はこうなりました。[GONE] が「削除されて動かない」、[OK] が「まだ使える」です。
# Python 3.15.0b2 [GONE] sre_compile module: ModuleNotFoundError [GONE] sre_parse module: ModuleNotFoundError [GONE] sre_constants module: ModuleNotFoundError [GONE] pathlib.PurePath.is_reserved(): AttributeError [GONE] http.server.CGIHTTPRequestHandler: AttributeError [GONE] types.CodeType.co_lnotab: AttributeError [OK] locale.getdefaultlocale()
sre_compile などは正規表現の内部実装用モジュールで、普通は直接 import しません。ただ、一部のライブラリが内部で触っていることがあるので、3.15で急に動かなくなったライブラリがあれば、ここが原因かもしれません。
面白かったのが最後の locale.getdefaultlocale() です。3.12で実行すると「3.15で削除予定」と明確に警告されるのに、肝心の3.15ベータ版ではまだ残っていました。ドキュメント上は削除予定の扱いですが、ベータ2の時点では消えていません。正式版で消える可能性が高いので、警告が出ているうちに getencoding() などへ移しておくのが安全です。「ドキュメントの予定」と「実際の実装」がずれることがある、というのはベータ版を触ってみて初めて分かることでした。
このほか、正規表現の re.match() も「文字列の先頭から一致を調べる」という意味が紛らわしいとして、re.prefixmatch() という名前が新しく用意され、re.match() はゆるやかに非推奨になりました(今すぐ消えるわけではありません)。
3.15へアップグレードすべき?
正式版は2026年10月です。今すぐ本番を上げる話ではありませんが、3.15は「待つ価値のあるバージョン」だと思います。とくに遅延importは、起動の遅さに悩んでいたコマンドラインツールや、たくさんのライブラリを読み込むアプリにとって、わかりやすい効果があります。
一方で、UTF-8の標準化と古いAPIの削除は、移行時に挙動が変わる可能性がある部分です。やることはシンプルで、ベータ版を入れて自分のコードやテストを一度通してみるだけです。uv python install 3.15 で並行して入れられるので、今のPythonはそのままにしてCIで試すのがおすすめです。本記事の検証コードはGitHubのリポジトリにまとめてあるので、手元での確認の出発点に使ってください。
高速化という観点では、今回触れなかったJITコンパイラの改善も大きいです。そちらはPython 3.15 JIT、軌道に復帰にまとめています。また、Python開発の屋台骨であるツール周りの動きとしてはOpenAIがruff・uvの開発元を買収した話もあわせてどうぞ。
Python 3.15はいつリリースされますか?
uv python install 3.15で誰でも試せます。Python 3.15の目玉機能は何ですか?
UTF-8がデフォルトになると何が変わりますか?
Python 3.15で使えなくなるコードはありますか?
検証環境と参照元
検証環境: Python 3.15.0b2(2026年6月11日ビルド) / Linux aarch64 / 比較対象は Python 3.12.13。数値はいずれも手元で計測した実測値です。ベータ版のため、正式版で結果が変わる可能性があります。