2025年2月: Python 3.13で更新された機能の紹介(kadowaki)¶
門脇(@satoru_kadowaki)です。 2025年2月の「Python Monthly Topics」は、Python 3.13でアップデートされた機能について紹介します。
ご存知のように、Python 3.13は2024年10月にリリースされ、多くの改善と新機能が追加されています。
本記事では、具体的にどのような点がアップデートされているのか、主な機能をピックアップして紹介していきます。
新しい対話型インタプリタとエラーメッセージ¶
今回のリリースにおける最も大きな変更点の1つとして、新しい対話型インタプリタがあります。 また、エラーメッセージはPython 3.10以降継続的に改善されており、バージョンが上がるごとにエラー内容が理解しやすくなり、プログラム修正が効率的に行えるよう進化してきました。
最初にこの2つについて簡単に紹介します。
対話型インタプリタの改善¶
新しい対話型インタプリタの主な特徴には以下があります。
プロンプトやトレースバックがデフォルトでカラー表示され、視認性が向上した
関数やクラスなどの複数行にわたるコードの編集が可能になった
exitやquitなどのコマンドが括弧なしで実行できるようになった
ファンクションキーへ機能が割り当てられた
F1キー: ヘルプの表示
F2キー: コマンドの履歴をブラウズ
F3キー: ペーストモードで大きなコードブロックを貼り付け
参考までに、実際にインタプリタで実行した結果は以下の通りです。
さらに改善されたエラーメッセージ¶
先述のとおり、インタプリタの更新に伴いエラーメッセージもカラー化されました。 エラーの種類や関係するコード部分が色分けされることで、視覚的に重要な情報を簡単に確認できるようになりました。
具体的に見てみましょう。
Python 3.12までのエラーメッセージは、以下のようなコードでは IndexError: list index out of range
と表示されていました。
Python 3.13でもエラーメッセージ自体は同じですが、以下のように具体的なエラー部分がカラー表示で示されるようになっています。
また、標準入力については <python-input-X>
のように変更され、末尾の数字はインタプリタで実行された入力順に番号が表示されています。
標準ライブラリ・モジュールの衝突がより明確に¶
エラーメッセージは、標準ライブラリやモジュールの名前との衝突についても改善されています。 たとえば以下のようなファイル構成で、スクリプトファイルsecrets.pyが標準ライブラリであるsecretsと衝突している状態があるとします。
$ tree
.
├── example_err.py
└── secrets.py
サンプルのため、secrets.pyは空にして以下のexample_err.pyを実行してみます。
import secrets
print(secrets.token_bytes())
上記をPython 3.12で実行すると、以下のように AttributeError: module 'secrets' has no attribute 'token_bytes'
だけが返されます。
$ python3.12 example_err.py
Traceback (most recent call last):
File "/home/ubuntu/example_err.py", line 3, in <module>
print(secrets.token_bytes())
^^^^^^^^^^^^^^^^^^^
AttributeError: module 'secrets' has no attribute 'token_bytes'
Python 3.13では同じメッセージの他に、スクリプトファイル名が標準ライブラリのモジュール名と衝突していることが具体的に示されます。
(サンプルの出力結果は、見やすくするために改行をいれています。)
出力結果を確認すると、 consider renaming.../secrets.py
部分でファイル名の変更を促すとともに、
the same name as the standard library module named 'secrets'
という部分で標準ライブラリのsecretsモジュールと同じ名前であることが示されています。
エラーメッセージが具体的に問題を指摘してくれることで、より迅速に問題を把握することができるのはとてもありがたいです。
$ python3.13 example_err.py
Traceback (most recent call last):
File "/home/ubuntu/example_err.py", line 3, in <module>
print(secrets.token_bytes())
^^^^^^^^^^^^^^^^^^^
AttributeError: module 'secrets' has no attribute 'token_bytes'
(consider renaming '/home/ubuntu/secrets.py' since it has
the same name as the standard library module named 'secrets'
and prevents importing that standard library module)
パフォーマンス向上にむけた取り組み¶
続いてはパフォーマンスに関する内容について見ていきます。 Python 3.13ではパフォーマンス向上を目的として、実験的に以下が導入されました。
実験的なJIT (Just-In-Time) コンパイルの導入¶
Python 3.13では、PEP 744 で実験的なJITコンパイラが追加されました。
JITコンパイラは、関数などの頻繁に実行されるコードを検出し、最適化します。
これにより実行速度の向上が期待されますが、現時点ではデフォルトで無効となっています。
利用するには --enable-experimental-jit
オプションを指定してPythonをビルドする必要があります。
以下に具体的なビルド手順を記載しています。 今回は、ubuntu(Linux)を使用してビルドを行っています。
# 関連ライブラリの更新/インストール
$ sudo apt update
$ sudo apt install build-essential zlib1g-dev \
> libncurses5-dev libgdbm-dev libnss3-dev libssl-dev \
> libreadline-dev libffi-dev curl clang
# Python 3.13.2をダウンロード
$ curl -O hhttps://www.python.org/ftp/python/3.13.2/Python-3.13.2.tgz
$ tar zxvf Python-3.13.2.tgz
$ cd Python-3.13.2
# Pythonをビルド JITを有効にするオプションと最適化オプションを指定
$ ./configure --enable-experimental-jit --enable-optimizations
$ make
実際にシステムにインストールしたい場合は、追加で sudo make install
を実行する必要があります。
今回は確認を行うだけですので、Python-3.13.2フォルダ内にビルドされたPythonを使用して実行してみます。
サンプルコードは以下を使用します。 JITによる効果を得るために、単純な数値計算部分を関数化して時間計測を実施します。
import time
# JITパフォーマンス計測のため単純な数値計算を行う関数
def simple_calc(n: int) -> int:
res = 0
for i in range(1, n):
res += (i * 3.14159) ** 2
return res
start = time.time()
n = 10**8 # 計算回数(大きいほど効果が出やすい)
result = simple_calc(n)
end = time.time()
duration = end - start # 処理時間を計測
print(f"実行時間: {duration:.3f} 秒")
まずは、システムにインストール済みのPython 3.12を使用して実行してみます。
$ python3.12 example_jit.py
実行時間: 5.203 秒
実行結果は5秒程度でした。
続いて、手動ビルドしたPython 3.13で実行してみます。
実行時にJITを有効にするには、環境変数に PYTHON_JIT=1
を指定して実行します。
# (実行ディレクトリ) ./Python-3.13.2/
$ PYTHON_JIT=1 ./python ../example_jit.py
実行時間: 3.716 秒
実行結果は4秒弱となりました。 サンプルコードでは30%以上の速度改善が得られましたが、JITによる最適化では以下のようなコードで効果を得られやすいです。
ループが多用されるコード: ループ処理では、同じ処理が何度も実行されるため、高速化が期待できます
数値計算やCPUバウンドな処理: 主にCPUを使う処理では、JITの最適化が直接的にパフォーマンス向上につながります
頻繁な関数呼び出しが行われる処理: 繰り返し実行される関数は、オーバーヘッドなどを削減する効果が期待できます
前述のとおり、JITの導入はPython 3.13では実験的なものですが、今後のリリースに向けて継続して更新されることが予想され、さらに改善される見込みです。 なお、以下のPyCon US 2024のレポートには、JITに関する開発の背景についての記載があります。興味のある方はぜひ読んでみてください。
PyCon US 2024 Day 3とスプリント - Building a JIT compiler for CPython
GIL (Global Interpreter Lock) の無効化¶
JITと同様に、Python 3.13では (PEP 703) で実験的にフリースレッドモードが導入されました。 フリースレッドモードではGILを無効化し、マルチスレッドでのパフォーマンス向上を目的としています。
GILとは、Pythonのインタプリタが一度に1つのスレッドのみを実行できるようにするロックのことです。 Pythonにおける大きな制約で、GILの存在がパフォーマンスのボトルネックとなることがしばしばあります。
Python 3.13では、--disable-gil
オプションを使用してビルドすることで実験的にGILの排他制御を無効化することができます。
また、以下のリンクにある手順を行えば、すでにビルド済みのバイナリを試してみることも可能です。
GILの無効化についても、安定性やパフォーマンスが通常の利用において必ずしも改善されるわけではなく、まだ実験的な段階です。 将来的な改善に期待しましょう!
型ヒント¶
Python 3.13では、型ヒントについても柔軟性や安全性を向上させるアップデートが導入されています。 改善内容を簡単にまとめると以下になります。
PEP |
変更内容 |
---|---|
TypeVarにデフォルト値の設定が可能に |
|
warnings.deprecated()デコレータで関数やクラスの非推奨を警告し、型チェッカーでも確認できる |
|
typing.ReadOnlyでTypedDictのフィールドを読み取り専用(変更不可)として定義し、型チェッカーで確認できる |
|
typing.TypeIsをTypeGuardの代替として、より直感的な型ナローイングが可能に |
上記のうち、本記事では warnings.deprecated()
と typing.ReadOnly
を実際に試してみます。
warnings.deprecated() デコレータ¶
Pythonで開発を行っていると、ある関数やクラスなどが将来的に廃止される可能性があり、非推奨であることを利用者に知らせたいことがあります。
Python 3.12以前では関数やクラスの非推奨警告をするには warnings.warn()
関数を使用して、関数やクラス内に記述していました。
3.13からは warnings.deprecated()
デコレータを使用して行えます。
具体的な使い方は後述しますが、詳細については以下の公式ドキュメントを参照ください。
https://docs.python.org/ja/3.13/library/warnings.html#warnings.deprecated
以下がサンプルコードです。コメントで3.12以前の記述方法も記載してあります。
import warnings
# デコレータでdeprecation warningを出す
@warnings.deprecated("Use `new_func()` instead.")
def old_func():
# 3.12以前の書き方
# warnings.warn("Use `new_func()` instead.", DeprecationWarning)
return "result (old)"
def new_func():
return "result(new)"
print(old_func()) # 実行時に警告
実行結果は以下のようになります。
$ python3.13 example_warn.py
/home/ubuntu/example_warn.py:15: DeprecationWarning: Use `new_func()` instead.
print(old_func()) # 実行時に警告
result (old)
warnings.deprecated()
デコレータを使用するメリットとして、 Mypy などの型チェッカーでも非推奨を確認できる点があります。
Mypy 1.14以降では、以下のように --enable-error-code=deprecated
オプションを使用してチェックできます。
mypy example_warn.py --enable-error-code=deprecated
example_warn.py:15: error: function example_warn.old_func is deprecated: Use `new_func()` instead. [deprecated]
Found 1 error in 1 file (checked 1 source file)
typing.ReadOnlyで読み取り専用の要素を定義¶
Python 3.12以前では、TypedDictの値は可変で、型システム上で変更を防ぐ手段はありませんでした。
Python 3.13では以下のように、TypedDictの一部のキーの値が変更不可能であることを ReadOnly
を使用して定義できるようになりました。
from typing import TypedDict, ReadOnly
class Lang(TypedDict):
name: ReadOnly[str] # 読み取り専用
age: int # 変更可能
lang: Lang = {"name": "Python", "age": 34}
lang["age"] = 35 # OK(変更可能)
lang["name"] = "Rust" # 型エラー(変更不可)
Mypyを使用した型チェックにおいても、以下のように ReadOnly TypedDict key
とエラーが返されます。
$ mypy example_readonly.py
example_readonly.py:12: error: ReadOnly TypedDict key "name" TypedDict is mutated [typeddict-readonly-mutated]
Found 1 error in 1 file (checked 1 source file)
型ヒントのアップデートは一見すると小さな変更に思えますが、これまで対応しきれなかった部分が改善され使いやすくなっており、進化を実感できる内容となっています。
その他のアップデート¶
ここからは筆者が個人的に選ぶ「主要なアップデートの陰に隠れがちだけど、知っておきたい」内容をご紹介します。
os.process_cpu_count() 関数¶
1つ目は、osモジュールに追加された process_cpu_count()
関数についてです。
process_cpu_count()
は、現在実行中のプロセスが使用できるCPUの論理コア数を返す関数です。
これまでも cpu_count()
関数を使用してシステム全体で利用可能なCPUコア数を取得できましたが、process_cpu_count()
関数ではシステムのCPUリソースを動的に確認できるため、並列処理を行う際にどの程度の並列を使うべきかを調整がしやすくなりました。
>>> import os
>>> print(os.process_cpu_count())
4
copy.replace() 関数¶
2つ目も標準ライブラリからですが、copyモジュールに replace()
関数が追加されました。
主な用途としては、「既存のオブジェクトを変更し、新しいオブジェクトを作成したい場合」が想定されます。
copy.replace()
関数のサポートには、以下が含まれています。
collections.namedtuple()
dataclasses.dataclass
datetime.datetime, datetime.date, datetime.time
これまでもnamedtupleには _replace()
関数、dataclassとdatetimeには replace()
関数が存在していましたが、
異なる型のオブジェクトが複数ある場合、それぞれのメソッドを使い分ける必要があり、isinstance()
関数などを用いて処理の分岐が必要でした。
copy.replace()
を使用することで、これらを統一的に扱えるようになるというメリットがあります。
import copy
from dataclasses import dataclass
from collections import namedtuple
from datetime import datetime, date
# 1. dataclass を使用したサンプル
@dataclass(frozen=True)
class LangData:
name: str
birth: datetime
py_data = LangData(name="Python", birth=datetime(1991, 2, 20))
ru_data = LangData(name="Rust", birth=datetime(2015, 5, 15))
# py_dataの一部を置き換えてコピー
py_data2 = copy.replace(py_data, birth=copy.replace(py_data.birth, year=2000))
print(py_data2) # LangData(name='Python', birth=datetime.datetime(2000, 2, 20, 0, 0))
# 2. namedtuple を使用した LangData
LangTuple = namedtuple("LangTuple", ["name", "birth"])
py_tuple = LangTuple(name="Python", birth=date(1991, 2, 20))
ru_tuple = LangTuple(name="Rust", birth=date(2015, 5, 15))
# ru_tupleの一部を置き換えてコピー
ru_tuple2 = copy.replace(ru_tuple, birth=copy.replace(ru_tuple.birth, year=2020))
print(ru_tuple2) # LangTuple(name='Rust', birth=datetime.date(2020, 5, 15))
# 3. datetime の変更
dt1 = datetime(2025, 2, 20, 14, 30)
dt2 = copy.replace(dt1, year=2030)
print(dt2) # 2030-02-20 14:30:00
プラットフォームサポート¶
3つ目は、Pythonのプラットフォームサポートについてです。 Pythonのプラットフォームは「Tier」というカテゴリに分けられています。 レベルが高い方からTier 1、Tier 2、Tier 3に分けられており、Tier 1が最もサポートレベルが高いカテゴリーです。[1]
Python 3.13では、以下のような プラットフォームサポートの更新が行われました。
AppleのiOSとAndroidがTier 3に追加され、公式サポート対象に
wasm32-wasi(WASM (WebAssembly) 向けの WASI(WebAssembly System Interface))が Tier 2 に昇格
1.については、いずれもまだ制約はありますが、モバイルOS上でPythonスクリプトが動作する環境が着実に整い始めていると考えられます。
2.については、ブラウザ外でWebAssemblyを使うサーバーレスアプリケーション環境で、Pythonの利用がさらにしやすくなることが期待されます。
一方で、WebブラウザでPythonを動かすためEmscriptenを使用したWebAssembly wasm32-emscripten
は、公式サポート外になりました。
このあたりは、今後の動向が気になるところです。
まとめ¶
今回はPython 3.13のリリースから注目される部分を中心に紹介しました。 パフォーマンスの最適化、標準ライブラリの改善、エラーメッセージの強化など多くの進化が見られ、特にJITの試験導入などスクリプトの実行速度向上に関しては具体的な取り組みが進んでいます。
また、Python 3.13では、PEP602 に基づいた年次リリースサイクルも更新され、以下のような変更がありました。
フルサポート(バグ修正)の期間が従来の1年半から2年に延長
その後のセキュリティフィックスの期間は3年に変更
この変更により、ユーザーはバグ修正を受けられる期間が長くなり、より安定した環境で長く利用できるようになったことも嬉しいことです。 今後のリリースにも期待しつつ、最新バージョンのPythonを活用していきましょう!