Python 3.13ではlocals関数の挙動について標準化がなされた。これにより、同じコードを実行したときにその挙動が以前のバージョンとは異なる場合がある。このことを中心に幾つかの変更点(リリーススケジュールの変更など)を取り上げていこう。
前回はPython 3.13で実験的にサポートされたJITコンパイラ機能について調べた。今回はその他の新機能の中から幾つかを見ていくことにする。
locals関数は現在実行されている実行フレーム(モジュールレベル、関数、メソッドなど)におけるローカル変数とその値の組みを要素とする辞書(マッピング)を戻り値とする。Python 3.12の公式なドキュメントは次のようになっている。
注釈にある通り、この辞書の内容を変更してもインタープリタが使用するローカル変数の値にそれが影響を持たない点は覚えておく必要がある。コードで示すと次の通りだ。
def f():
x = 1
locals()['x'] = 10
print(x)
f()
これはローカル変数の値を直接書き換えるのではなく、locals関数の戻り値である辞書を介してそのキー'x'の値を変更するコードだ。Python 3.12での実行結果を以下に示す。
上のような結果になるのは、python.orgで配布されているCPythonでの挙動であり、Python 3.12までは実際にどうなるかはPython実装での定義に依存していた。しかし、Python 3.13ではlocals関数とその戻り値に対する変更がどのようになるかが、PEP 667によって標準化された。簡単にいうと、多くのスコープではこれまでのCPythonの挙動が標準の挙動として標準化された。ただし、関数やジェネレータ、コルーチン、内包表記、ジェネレータ式のスコープ(これを「optimized scope」と呼ぶ)については、現在のローカル変数のスナップショットが戻り値となり、これを変更しても実際のローカル変数の値には何の影響もないことが明確化された。
Python 3.13でのlocals関数のドキュメントは次のようになっている。
Python 3.12までのlocals関数のドキュメントと比べると記述がかなり増え、「この場合にはこうなる」といったことが明確になっていることが分かる。
このことに関連して、フレームオブジェクトにも変更がある。フレームオブジェクトとは、プログラムの開始時や関数などの呼び出し時に新たに作成されるもので、Pythonのコードを実行する上で必要な情報(コードオブジェクトやローカル変数、グローバル変数、呼び出し元のフレーム情報など)を格納し、デバッグやトレースの目的で使われることが多い。このフレームオブジェクトにも今述べたようにローカル変数に関する情報が含まれていて、それらはf_locals属性として取得できる。
例えば、以下のコードはsysモジュールが提供する_getframe関数でフレームオブジェクトを取得して、そのf_locals属性を介して、ローカル変数の値を変更しようとするものだ。
import sys
def f():
x = 1
fr = sys._getframe()
fr.f_locals['x'] = 10
print(x)
f()
これをPython 3.12で実行すると次のような結果になる。
これはローカル変数の一覧をlocals関数で取得した場合と同様の結果だ。一方、上のコードをPython 3.13で実行すると次のようになる。
Python 3.12での実行結果とは異なり「10」が出力された。つまり、f_locals属性を介したローカル変数の値の変更は、現在の実行フレームにあるローカル変数の値に影響するということだ。
この挙動について、PEP 667では次のように述べられている(筆者による訳)。
関数スコープ(より広範にoptimized scope)において、f_locals属性は現在のローカル変数一覧に対するビュー(ライトスループロキシ)となり、f_locals属性を介してローカル変数の値を変更すると、それは即座にローカル変数に影響するようになった。実際、関数スコープでf_locals属性の型を調べると単なる辞書ではなくなっている。
import sys
def f():
fr = sys._getframe()
print(type(fr.f_locals))
f()
このコードをPython 3.12で実行すると、f_locals属性は通常の辞書であることが分かる。
一方、Python 3.13でこのコードを実行すると、f_locals属性がFrameLocalsProxyオブジェクトになっている。
locals関数の挙動が標準化されたことで、これまでとはコードの実行結果が異なるようになることもある。以下はその例だ。
def f():
print(locals())
exec('print(locals()); x = 1; print(locals())')
print(locals())
f()
これはexec関数で変数xを定義し、その前後でlocals関数を呼び出して、ローカル変数の一覧を取得するものだ。Python 3.12で実行すると、次のような結果になる。
しかし、Python 3.13ではこのような変更は常に無視されるようになった。
このような挙動の違いには注意する必要がある。
Python 3.9からはサポート期間は全体で5年となっていて、その内訳は次のようになっていた。
サポート期間が全体で5年なことは変わりないが、Python 3.13以降はフルサポートの期間が2年間(24カ月)に延長され、その分、セキュリティフィックスの期間が3年(36カ月)に短縮された
これはPython 3.13以降の話であり、Python 3.9からPython 3.12に関してはフルサポートが18カ月、セキュリティフィックスが42カ月のままであることには注意しよう。
フルサポート期間が延びたことで、自分が今使っているバージョンのPythonをより長く安心して使用できるようになる人もいるはずだ。
Pythonでは公式にサポートされるプラットフォームは幾つかの階層(tier)に分類されている。
tier | 内容 |
---|---|
tier 1 | 全ての開発者がtier 1に含まれるプラットフォームが正しく動作することについて責任を持つ。ここに含まれるプラットフォーム用のビルドの失敗はリリースをブロックする |
tier 2 | 少なくとも2人のコア開発者がそのプラットフォームのサポートに割り当てられる。ここに含まれるプラットフォーム用のビルドの失敗はリリースをブロックする |
tier 3 | 少なくとも1人のコア開発者がそのプラットフォームのサポートに割り当てられる。問題があってもリリースはブロックされない |
Pythonのプラットフォームサポートを分類するtier |
最重要なのはtier 1に含まれるプラットフォームだ。例えば、WindowsやmacOS、Linux向けのCPythonはtier 1でサポートされる。
そして、Python 3.13ではiOSとAndroidが公式にサポートされるプラットフォームとなった。ただし、現在はtier 3でのサポートとなっている。
その他にも新機能や変更点が多々ある。筆者の目に付いたものを幾つか列挙して、Python 3.13の新機能についての連載を終わることにしよう。
randomモジュールのコマンドラインインタフェースについては簡単に説明しておこう。簡単にいえば以下の3つの使い方がある。
1つ目はrandom.choiceメソッドを使って、選択肢の中からどれか1つを選択する。2つ目は1から指定された整数値の範囲からランダムな整数値を選択する。3つ目はrandom.uniformメソッドを使って0から指定された浮動小数点数値までの範囲から浮動小数点数値を選択する(一様分布)。
以下に実際に試している例を示す。
文字列を1つだけ指定した場合には、その中から1文字が選ばれるのではなく、常にその文字列が選ばれるので注意してほしい。晩ごはんに何のお弁当を買おうかと悩むときにはこのコマンドラインインタフェースが役に立つかもしれない。
Copyright© Digital Advantage Corp. All Rights Reserved.