Python3.14の新機能の実験と紹介
はじめに
Python3.14.0 が2025/10/7 にリリースされました。
この記事では新機能のサンプルコードを示しつつ紹介します。
今回の調査については以下の環境で行なっています。
そのため、途中のサンプルコードは以下の環境でのみ動作します。
Pythonの情報:
import sys
>>> sys.version_info
sys.version_info(major=3, minor=14, micro=0, releaselevel='final', serial=0)
>>> sys._is_gil_enabled()
True
マシン環境:
- macOS 15.4.1
- 2 GHz クアッドコアIntel Core i5
- 16 GB 3733 MHz LPDDR4X
Python3.14の新機能
- annotationの遅延評価
- マルチインタープリタ
- Template String
- 括弧なしの except および except* 式を許可する
- finally区のフローの制限
- zstdのサポート
- CPython 用の安全な外部デバッガインターフェース
- Asyncioイントロスペクション機能
詳細については下記のドキュメントを参照してください。
annotationの遅延評価
annotationは変数や関数のメタ情報をつける仕組みで、型ヒントなどで利用されます。
たとえばPython3.13などでは以下のように変数や関数、クラスのannotationを確認できます。
x :int = 3
y: list[str] = ["a", "b"]
print("__annotations__", __annotations__)
def y(a: str, b:int)->list[str]:
return []
print("y", y.__annotations__)
class C:
a: int = 1
b: int = 2
print("C", C.__annotations__)
このannotationが遅延評価されるようになり、以下のような実装が正常に動作するようになりました。
def f(x: Cls) -> None: ...
class Cls: pass
# 3.14 の遅延評価では:
# {'x': <class 'Cls'>, 'return': None}
print(f.__annotations__)
python3.13以前ではNameError: name 'Cls' is not definedとなり、正しく動かすには次のように宣言する必要があります。
def f(x: 'Cls') -> None: ...
class Cls: pass
また、__annotations__の変わったため一部挙動が変わっています。たとえば、python3.14で以下のコードを動かすとname '__annotations__' is not defined.となります。[1]
x :int = 3
y: list[str] = ["a", "b"]
print("__annotations__", __annotations__)
__annotations__を使用するかわりにannotationlibを使用してannotationの情報を取得することが推奨されています。
from annotationlib import get_annotations
import sys
x :int = 3
y: list[str] = ["a", "b"]
print(get_annotations(sys.modules[__name__]))
def y(a: str, b:int)->list[str]:
return []
print(get_annotations(y))
class C:
a: int = 1
b: int = 2
print(get_annotations(C))
def f(x: Cls) -> None: ...
class Cls: pass
# 3.14 の遅延評価では:
# {'x': <class 'Cls'>, 'return': None}
print(get_annotations(f))
参考資料
- PEP 649 – Deferred Evaluation Of Annotations Using Descriptors
- PEP 749 – Implementing PEP 649
- What’s new in Python 3.14 - PEP 649 & PEP 749: Deferred evaluation of annotations
- What’s new in Python 3.14 - Changes in annotations (PEP 649 and PEP 749)
マルチインタープリタ
PythonではインタープリタがPythonのスクリプトファイルをコンパイルし、バイトコードを実行しています。
PythonではGlobal Interpreter Lock(GIL)が存在しており、同じインタープリタではバイトコードを実行するスレッドが1つに制限されます。この機構は安全にPythonを動かす反面、並行性(Concurrency)は実現できるが並列性(Parallelism)を実現できない障害となっています。
Python3.14ではインタープリタを複数動かすことができるようになりました。すなわちGILが並列性(Parallelism)の実行の障害になりません。
以下のコードは一つのインタープリタをマルチスレッドで動かすケースと、複数のインタープリタで動かすケースの違いを表す例です。
実験コード
サンプルコード
from concurrent.futures import InterpreterPoolExecutor, ThreadPoolExecutor
import os, threading, sysconfig, sys, math, time, builtins
def work(limit: int):
start = time.perf_counter()
print(f"start... count_primes limit:{limit} pid:{os.getpid()} thread_id:{threading.get_ident()} id(main): {id(sys.modules['__main__'])} id(builtins): {id(builtins)}", flush=True)
cnt = 0
for n in range(2, limit):
root = int(math.isqrt(n))
for d in range(2, root + 1):
if n % d == 0:
break
else:
cnt += 1
print('end... count_primes', limit, time.perf_counter() - start, flush=True)
return cnt
if __name__ == "__main__":
# GILが有効を確認s
print("free-threaded build? ", bool(sysconfig.get_config_var("Py_GIL_DISABLED")))
print("GIL enabled at runtime:", getattr(sys, "_is_gil_enabled", lambda: True)())
pattern = [1_000_000, 500_000, 900_000]
print(f"id(main): {id(sys.modules['__main__'])} id(builtins): {id(builtins)}")
print('****** 直列')
start = time.perf_counter()
results = [work(p) for p in pattern]
print(time.perf_counter() - start, results)
print('****** ThreadPoolExecutor')
start = time.perf_counter()
with ThreadPoolExecutor() as pool:
results = list(pool.map(work, pattern))
print(time.perf_counter() - start, results)
print('****** InterpreterPoolExecutor')
start = time.perf_counter()
with InterpreterPoolExecutor() as pool:
results = list(pool.map(work, pattern))
print(time.perf_counter() - start, results)
実行結果
free-threaded build? False
GIL enabled at runtime: True
id(main): 4539170176 id(builtins): 4538709984
****** 直列
start... count_primes limit:1000000 pid:17971 thread_id:140704398269376 id(main): 4539170176 id(builtins): 4538709984
end... count_primes 1000000 4.183276683965232
start... count_primes limit:500000 pid:17971 thread_id:140704398269376 id(main): 4539170176 id(builtins): 4538709984
end... count_primes 500000 1.098561302991584
start... count_primes limit:900000 pid:17971 thread_id:140704398269376 id(main): 4539170176 id(builtins): 4538709984
end... count_primes 900000 2.609074859006796
7.891064839961473 [78498, 41538, 71274]
****** ThreadPoolExecutor
start... count_primes limit:1000000 pid:17971 thread_id:123145587011584 id(main): 4539170176 id(builtins): 4538709984
start... count_primes limit:500000 pid:17971 thread_id:123145603801088 id(main): 4539170176 id(builtins): 4538709984
start... count_primes limit:900000 pid:17971 thread_id:123145620590592 id(main): 4539170176 id(builtins): 4538709984
end... count_primes 500000 3.4312552110059187
end... count_primes 900000 6.604183859017212
end... count_primes 1000000 7.053750675986521
7.054385666968301 [78498, 41538, 71274]
****** InterpreterPoolExecutor
start... count_primes limit:1000000 pid:17971 thread_id:123145620590592 id(main): 4555515168 id(builtins): 4552875904
start... count_primes limit:900000 pid:17971 thread_id:123145587011584 id(main): 4553418016 id(builtins): 4550778752
start... count_primes limit:500000 pid:17971 thread_id:123145603801088 id(main): 4554466592 id(builtins): 4551827328
end... count_primes 500000 1.2162047760211863
end... count_primes 900000 2.9336666130111553
end... count_primes 1000000 3.3933817240176722
3.4818163299933076 [78498, 41538, 71274]
この実験では直列では7.9秒程度、ThreadPoolExecutorでは7秒程度、InterpreterPoolExecutorでは3.5秒程度となっています。
ThreadPoolExecutorはGILが有効なため、同時実行の効果が限られていますが、InterpreterPoolExecutorではその制限がないことがわかります。
また、builtinsやsys.modules['__main__']が別のIDとなっており、独立している環境であることが観測できます。
現時点ではマルチインタープリタにはいくつかの制限があります。
- 各インタープリタの起動はまだ最適化されていない
- 各インタープリタは必要以上にメモリを使用します(インタープリタ間の広範な内部共有の作業は継続中です)
- インタープリタ間でオブジェクトやその他のデータを実際に共有するためのオプションはまだ多くありません(現時点でmemoryview と Queue に限られる)
- PyPI 上の多くのサードパーティ拡張モジュールはまだ複数のインタープリタと互換性がありません (すべての標準ライブラリ拡張モジュールは互換性があります)
- 複数の独立したインタープリタを使用するアプリケーションを書くアプローチは、今のところPythonユーザーにはほとんど馴染みがありません。
参考資料
- What’s new in Python 3.14 - PEP 734: Multiple interpreters in the standard library
- PEP 734 – Multiple Interpreters in the Stdlib
- InterpreterPoolExecutor
Template String
f-stringは「{...} を評価して即座に文字列へ埋め込む」仕組みです。
そのため埋め込む“直前”に値をフックして加工・検査する手段がありません。
その結果、SQLやHTMLなどを作る場合に安全に使用できないケースが存在します。
以下のコードはf-stringとt-stringの挙動を比べたものとなります。
from html import escape
from string.templatelib import Template, Interpolation
user_input = '<img src=x onerror=alert(1)>'
print('f-string ----------')
html_f = f"<p>Hello, {user_input}</p>"
print(html_f)
# => <p>Hello, <img src=x onerror=alert(1)></p> ← 危険(イベントハンドラが生きたまま)
print('t-string------------')
def html(tmpl: Template) -> str:
"""
t-string から静的部はそのまま、補間値だけ HTML エスケープして連結
あくまで例なので、プロダクトコードには使用しないこと
"""
out = []
for part in tmpl:
if isinstance(part, Interpolation):
val = part.value
print(' ', val, part.expression, part.format_spec)
# 属性辞書を特別扱い: key="value" に展開
if isinstance(val, dict):
attrs = []
for k, v in val.items():
if v is None:
continue
attrs.append(f'{k}="{escape(str(v), quote=True)}"')
out.append(" ".join(attrs))
else:
# 通常の補間は HTML エスケープ
out.append(escape(str(val), quote=True))
else:
# 静的テキストはそのまま
out.append(part)
return "".join(out)
safe = html(t"<p>Hello, {user_input}</p>")
print(safe)
# => <p>Hello, <img src=x onerror=alert(1)></p> ← 無害化(テキスト化される)
参考資料
- PEP 750 – Template Strings
- What’s new in Python 3.14 - PEP 750: Template string literals
- 2.5.8. t-strings
括弧なしの except および except* 式を許可する
python3.13以前では以下の書き方は許容されていませんでしたが、3.14で許容されるようになりました。
try:
pass
except ValueError, ZeroDivisionError:
print('Error!')
参考資料
- What’s new in Python 3.14 - PEP 758: Allow except and except* expressions without brackets
- PEP 758 – Allow except and except* expressions without parentheses
finally区のフローの制限
finally区にreturn, break, continueを実行すると警告が出力されるようになりました。
たとえば、以下のようにfinally区でreturnを行った場合、その例外やreturnは握りつぶされます。
def f():
try:
1 / 0
return "...1"
except:
return "...2"
finally:
return "...3" # ← ここで例外が再送出されない
print(f()) # ...3となる
この挙動を警告する意図で、finally区でreturn, break, continue区があると警告が発生するようになりました。
参照
- What’s new in Python 3.14 - PEP 765: Control flow in finally blocks
- PEP 765 – Disallow return/break/continue that exit a finally block
zstdのサポート
zstdによる圧縮、解凍がサポートされました。
from compression import zstd
import math
# zstdで圧縮解凍
# https://ja.wikipedia.org/wiki/Zstandard
data = str(math.pi).encode() * 20
print("元データ:", data)
compressed = zstd.compress(data)
print(compressed)
ratio = len(compressed) / len(data)
print(f"Achieved compression ratio of {ratio} 圧縮サイズ:{len(compressed)}")
# 伸張
restored = zstd.decompress(compressed)
print("復元データ:", restored)
assert restored == data
参考
- compression.zstd — Compression compatible with the Zstandard format
- What’s new in Python 3.14 - PEP 784: Zstandard support in the standard library
- PEP 784 – Adding Zstandard to the standard library
- Zstandard
CPython 用の安全な外部デバッガインターフェース
sys.remote_execを使用して、起動中のプロセス上で指定のスクリプトを実行することが可能になりました。
これにより起動中のプロセスに対して、プロセスの再起動なしにデバッグすることが可能になります。
ただし、権限の問題があるので、必要に応じてsudoを実行してください。
remote_execの使用例

フォルダ構成
├── controller.py
├── remote_script.py
└── target.py
target.py
スクリプトを実行する対象となるプロセスで動かすスクリプト
# target.py -- a long-running Python 3.14 process to be remote-exec'ed.
import os, time, pathlib, sys
pid = os.getpid()
pathlib.Path("/tmp/target.pid").write_text(str(pid))
print(f"TARGET PID: {pid}", flush=True)
# Keep the interpreter running to provide safe execution points.
try:
while True:
time.sleep(0.5)
except KeyboardInterrupt:
print("Exiting target...")
remote_script.py
リモートで実行するスクリプト
# remote_script.py -- this file is executed *inside* the target process via sys.remote_exec()
import os, pathlib, sys, time
# Simple observable side-effect
p = pathlib.Path("/tmp/remote_exec_ok")
p.write_text(f"remote code ran in PID {os.getpid()}\n")
# Also print to the target's stdout (visible in `docker compose logs -f app`)
print(f"[remote] Hello from remote execution in PID {os.getpid()}!", flush=True)
controller.py
remote_script.pyをtarget.pyの動くプロセスで実行させる
# controller.py -- call sys.remote_exec() from inside the same container.
# Usage:
# python controller.py [/abs/or/relative/path/to/script.py]
# If script path is omitted, defaults to /work/remote_script.py
import os, sys, pathlib, time
def main():
import sys as _sys
script = pathlib.Path(sys.argv[2])
pid = int(sys.argv[1])
if not script.exists():
print(f"ERROR: Script not found: {script}", file=sys.stderr); sys.exit(2)
print(f"Submitting remote_exec(pid={pid}, script={script}) ...", flush=True)
try:
# https://docs.python.org/ja/3/library/sys.html#sys.remote_exec
_sys.remote_exec(pid, str(script))
print("Submitted. Code will execute at the next safe execution point.")
except Exception as e:
print(f"remote_exec failed: {e}", file=sys.stderr); sys.exit(3)
if __name__ == "__main__":
main()
使い方:
- target.pyの起動
python target.py
- 新しいターミナルをひらいて、リモートスクリプトを実行
python 40284 ./remote_script.py
- 40284 は 1)のPID。
参考
- PEP 768 – Safe external debugger interface for CPython
- What’s new in Python 3.14 - PEP 768: Safe external debugger interface for CPython
- sys.remote_exec
Asyncioイントロスペクション機能
asyncio用のツールとしてpsコマンドとpstreeコマンドが追加されました。
# 当該プロセスで動作する非同期タスクの一覧が表示される
sudo python -m asyncio ps 非同期タスクが動作するプロセスID
# 非同期タスクのコールツリーを可視化
sudo python -m asyncio pstree 非同期タスクが動作するプロセスID
詳しい使用例については下記を参照してください。
その他
- エラーメッセージの改善
- Android用のバイナリリリースが行われる
- フリースレッドPythonが正式にサポート
- デフォルトはGIL有効なのでインストール時に選択する必要あり

- フリースレッド版のDockerイメージは自分でソースから作る必要がありそう?
- おそらく多くのライブラリが未検証なので、使用は慎重にしたほうがよい
- PEP 779 – Criteria for supported status for free-threaded Python
-
Incremental garbage collection
- 最大停止時間が短くなる
- gc.collect(1)が第1世代を対象にガベージコレクションするという意味だったのが、若い世代と古い世代の増分を対象にという意味になる
- WindowsとmacOSのバイナリリリースでは、実験的なジャストインタイムコンパイラがサポート
- 環境変数
PYTHON_JIT=1で試せる - 適応的スペシャライザの特殊化結果を機械語化してさらに高速化する
- PEP 744 – JIT Compilation
- JIT
- 環境変数
-
Classやfunctionにたいしての
__annotations__は3.14でもエラーにはなりません。 ↩︎
Discussion