CPythonの高速化[3.13]
言語仕様ではありませんが、Pythonインタプリタの標準実装であるCPythonにさまざまな改良が施され、プログラムの実行がより高速になりました。これは、Python 3.11から始まった「Faster CPython project」に基づくものとされています。
なお、Python 3.13で導入予定だったガベージコレクタの改良は、RC2でパフォーマンス低下などの症状が見られたためRC3でロールバックされ、Python 3.14以降の実装になりました。このガベージコレクタは、インクリメンタルサイクリックガベージコレクタと呼ばれ、メモリの解放を段階的に行い、プログラムの中断やパフォーマンスの低下を防ぐための仕組みです。
フリースレッドモード
実験的実装ではありますが、フリースレッドモード(Free-threading mode)での実行がサポートされました。フリースレッドモードとは、同じインタプリタ内で複数のスレッドを並列実行できる機能です。マルチコアCPU環境でそのメリットを生かせるため、プログラムを高速に実行できます。
フリースレッドモードのサポートは、グローバルインタプリタロック(GIL; Global Interpreter Lock)の廃止で実現されます。グローバルインタプリタロックとは、その名の通りインタプリタ全体で大域的に一つのロックを持つことです。シングルスレッドのプログラムではロック機構がシンプルなので、速度が向上する、C言語などのスレッドセーフでないライブラリの利用が容易になる、などのメリットを得られます。その反面、マルチスレッドのプログラムでは並行性に制限が加わり、思うような性能を得られないデメリットが発生します。
Python 3.13では、このグローバルインタプリタロックをなくしたビルドを用いて、環境変数PYTHON_GILの設定かコマンドラインオプション-X gil=1を指定すると、フリースレッドモードで実行できます。グローバルインタプリタロックの廃止は破壊的変更とも位置付けられるので、完全に置き換えるのではなく、このように別のビルドを用意して徐々に移行する方針をとるようです。
JITコンパイラの実験的なサポート
こちらも実験的ではありますが、JIT(Just-in-Time)コンパイラがサポートされました。JITコンパイラは、スクリプト言語の速度面の弱点を克服する手段として非常に有効ですが、意外なことにPythonはこれまでJITコンパイラをサポートしていませんでした。JavaScriptはJITコンパイラをいち早く取り入れ、Rubyも2016年でJITコンパイラをMJITとしてサポート、2022年にはYJITを搭載しています。Pythonも、この流れに乗ってより高速化が進められています。
Python 3.13で取り入れられたJITコンパイラは、コピー&パッチ方式と呼ばれる新しい方式です。JITコンパイラは、CPythonのビルドにおいて--enable-experimental-jitオプションを指定した場合に有効となります。
新しいメモリアロケータ(mimalloc)
Python 3.12まではオプション的な位置付けであったメモリアロケータライブラリmimallocが、Python 3.13では標準となりました。mimallocはme-mallocと発音し、その中身はC言語の標準メモリ割り当て関数であるmallocの置き換えです。標準のmalloc関数に比べて性能的に優れており、それを使うプログラムにも性能上のメリットをもたらします。
Python 3.13においてmimallocが標準となったことで、メモリの確保や解放にかかわるあらゆる局面での性能向上が期待できます。
新しい型関連機能[3.13]
Pythonでは、型安全の向上のために型関連機能が随時強化、追加されています。地味ではありますが、Python 3.13でも新しい型関連機能が追加されています。いずれも、型チェッカに有効に働く機能です。
型変数における型パラメータの既定値の導入(typing.NoDefault)
Python 3.13では、typing.TypeVar、ParamSpec、TypeVarTupleにおける型パラメータの既定値をdefault引数によって指定することができるようになりました。この指定がない型パラメータは既定値がない状態になりますが、これを明示的に表すことができるようにするのがtyping.NoDefaultです。
typing.NoDefaultは、typing.TypeVarのdefault引数の既定値に設定されます。つまり、明示的に初期値を与えないとtyping.NoDefaultのままとなります。既定値は、__default__で参照することができます。
from typing import TypeVar, NoDefault T = TypeVar("T") print(T.__default__ is NoDefault) # True S = TypeVar("S", default=None) print(S.__default__ is None) # True
なお、Python 3.12で、TypeVarなどを利用しない新しい型引数構文が導入されましたが、そこでの既定値の指定は以下のようになります。
class Cls[T = NoDefault]: # 既定の型を指定 def default(self): print(T.__default__) c = Cls() c.default() # typing.NoDefault
ユーザ定義型の関数戻り値の新しい注釈(typing.TypeIs)
typing.TypeIsは、型の絞り込み(type narrowing)のための新しい型です。型の絞り込みとは、第3回でも少し触れた通り、型チェッカによる型ガードのためにプログラムの処理から型の情報を取得することです。これには、同じく第3回で紹介したtyping.TypeGuardを戻り値とするユーザ定義関数が使われてきましたが、typing.TypeIsは、それを適切に実装し直したものと言えます。
TypeGuardにはいくつかの制限があり、例えば以下のリストのようなケースで、else節で型を正しく絞り込んでくれない問題がありました。
from typing import TypeGuard def is_integer(x: object) -> TypeGuard[int]: return isinstance(x, int) def increment(x: int | str): if is_integer(x): print(x + 1) # xはint else: print("".join([chr(ord(e)+1) for e in x])) # xがintでもあるとみなされる increment(10) increment("Python")
実行には問題ありませんが、型チェッカにおいてはelse節のxがintでイテレータがないと指摘されます。if節でintでないことがはっきりしているのにもかかわらず、です。このように、TypeGuardはTrueの場合には型を絞り込むことができますが、Falseの場合には全く絞り込みができません。これでは役割を果たせないとして、TypeIsが新たに設けられました。使い方はほぼ同様ですが、else節が期待される動作となります。上記のリストのTypeGuardをTypeIsに置き換えるだけです(配布サンプルのtypeis.py)。
TypeGuardの動作を変更することも検討されたようですが、TypeIsとの併存ということになったようです。Python 3.13以降では、TypeGuardよりTypeIsを優先して使うことが推奨されるでしょう。
TypedDictにおける読み出し専用要素の指定(typing.ReadOnly)
TypedDictは、型付き辞書と呼ばれ、key-value形式のデータ構造を型付きで表現できる型です。その要素に対して、型の注釈に加え、読み出し専用であることの注釈を付加するのが、typing.ReadOnlyです。読み出し専用に指定された要素に書き込もうとすると、型チェックでエラーとすることができます。要素を読み取り専用にするには、ReadOnly[型名]を注釈として付加します。
from typing import TypedDict, ReadOnly class Person(TypedDict): name: ReadOnly[str] age: int def modify_person(p: Person): p["age"] = 60 # 書き換えOK p["name"] = "Nao Yamauchi" # 書き換え不可
この場合、ageは書き換え可能ですが、nameは書き換えられません。イミュータブルな要素を指定できるようになり、安全性が向上します。