「Python 3.13」で変更されたlocals関数の振る舞いやその他の変更点Python最新情報キャッチアップ

Python 3.13ではlocals関数の挙動について標準化がなされた。これにより、同じコードを実行したときにその挙動が以前のバージョンとは異なる場合がある。このことを中心に幾つかの変更点(リリーススケジュールの変更など)を取り上げていこう。

» 2024年12月06日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「Python最新情報キャッチアップ」のインデックス

連載目次

 前回はPython 3.13で実験的にサポートされたJITコンパイラ機能について調べた。今回はその他の新機能の中から幾つかを見ていくことにする。

locals関数が返す辞書の内容を変更するセマンティクスの定義

 locals関数は現在実行されている実行フレーム(モジュールレベル、関数、メソッドなど)におけるローカル変数とその値の組みを要素とする辞書(マッピング)を戻り値とする。Python 3.12の公式なドキュメントは次のようになっている。

Python 3.12でのlocals関数のドキュメント Python 3.12でのlocals関数のドキュメント

 注釈にある通り、この辞書の内容を変更してもインタープリタが使用するローカル変数の値にそれが影響を持たない点は覚えておく必要がある。コードで示すと次の通りだ。

def f():
    x = 1
    locals()['x'] = 10
    print(x)

f()

変数xの値は10ではなく1

 これはローカル変数の値を直接書き換えるのではなく、locals関数の戻り値である辞書を介してそのキー'x'の値を変更するコードだ。Python 3.12での実行結果を以下に示す。

Python 3.12での実行結果 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.13でのlocals関数のドキュメント 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で実行すると次のような結果になる。

Python 3.12での実行結果 Python 3.12での実行結果

 これはローカル変数の一覧をlocals関数で取得した場合と同様の結果だ。一方、上のコードをPython 3.13で実行すると次のようになる。

Python 3.13での実行結果 Python 3.13での実行結果

 Python 3.12での実行結果とは異なり「10」が出力された。つまり、f_locals属性を介したローカル変数の値の変更は、現在の実行フレームにあるローカル変数の値に影響するということだ。

 この挙動について、PEP 667では次のように述べられている(筆者による訳)。

  • optimized scopeにおいてlocals関数はローカル変数のその場限りのスナップショットを提供し、他のスコープではローカル変数に対する読み込みアクセス/書き込みアクセスを提供する
  • フレームオブジェクトのf_locals属性は(optimized scopeを含む)全てのスコープにおいて読み込みアクセス/書き込みアクセスを提供する

 関数スコープ(より広範にoptimized scope)において、f_locals属性は現在のローカル変数一覧に対するビュー(ライトスループロキシ)となり、f_locals属性を介してローカル変数の値を変更すると、それは即座にローカル変数に影響するようになった。実際、関数スコープでf_locals属性の型を調べると単なる辞書ではなくなっている。

import sys

def f():
    fr = sys._getframe()
    print(type(fr.f_locals))

f()

f_locals属性の型を調べる

 このコードをPython 3.12で実行すると、f_locals属性は通常の辞書であることが分かる。

Python 3.12では関数スコープにおける実行フレームのf_locals属性は辞書 Python 3.12では関数スコープにおける実行フレームのf_locals属性は辞書

 一方、Python 3.13でこのコードを実行すると、f_locals属性がFrameLocalsProxyオブジェクトになっている。

Python 3.13では関数スコープにおける実行フレームのf_locals属性はFrameLocalsProxyオブジェクト Python 3.13では関数スコープにおける実行フレームのf_locals属性はFrameLocalsProxyオブジェクト

 locals関数の挙動が標準化されたことで、これまでとはコードの実行結果が異なるようになることもある。以下はその例だ。

def f():
    print(locals())
    exec('print(locals()); x = 1; print(locals())')
    print(locals())

f()

exec関数で変数xを定義するコード

 これはexec関数で変数xを定義し、その前後でlocals関数を呼び出して、ローカル変数の一覧を取得するものだ。Python 3.12で実行すると、次のような結果になる。

Python 3.12ではexec関数で定義した変数xがexec関数呼び出しの後もローカル変数として存在する Python 3.12ではexec関数で定義した変数xがexec関数呼び出しの後もローカル変数として存在する

 しかし、Python 3.13ではこのような変更は常に無視されるようになった。

Python 3.13ではexec関数で定義した変数はその呼び出し後にはローカル変数として存在しない Python 3.13ではexec関数で定義した変数はその呼び出し後にはローカル変数として存在しない

 このような挙動の違いには注意する必要がある。

リリーススケジュールの変更

 Python 3.9からはサポート期間は全体で5年となっていて、その内訳は次のようになっていた。

  • フルサポート(バグフィックス、インストーラーのリリース):バージョン3.X.0のリリース後18カ月
  • セキュリティフィックス(ソースコードのみのリリース):その後の42カ月:

 サポート期間が全体で5年なことは変わりないが、Python 3.13以降はフルサポートの期間が2年間(24カ月)に延長され、その分、セキュリティフィックスの期間が3年(36カ月)に短縮された

 これはPython 3.13以降の話であり、Python 3.9からPython 3.12に関してはフルサポートが18カ月、セキュリティフィックスが42カ月のままであることには注意しよう。

 フルサポート期間が延びたことで、自分が今使っているバージョンのPythonをより長く安心して使用できるようになる人もいるはずだ。

iOSとAndroidがプラットフォームとして公式にサポートされる

 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の新機能についての連載を終わることにしよう。

  • exec関数の書式はPython 3.12では「exec(object, globals=None, locals=None, /, *, closure=None)」だったのがPython 3.13では「exec(source, /, globals=None, locals=None, *, closure=None)」になった。これにより、キーワード引数の形でglobalsパラメーターとlocalsパラメーターに値を渡せるようになった。eval関数も同様
  • Python 3.12まではstr.replaceメソッドの書式は「str.replace(old, new[, count])」だったのが、Python 3.13では「str.replace(old, new, count=-1)」に変更された。これにより、str.replaceメソッドのcountパラメーターにキーワード引数として値を渡せるようになった
  • randomモジュールにコマンドラインインタフェースが追加された
  • 2to3プログラムおよびlib2to3モジュールが削除された

 randomモジュールのコマンドラインインタフェースについては簡単に説明しておこう。簡単にいえば以下の3つの使い方がある。

  • python -m random -c 選択肢
  • python -m random -i 整数値
  • python -m random -i 浮動小数点数値

 1つ目はrandom.choiceメソッドを使って、選択肢の中からどれか1つを選択する。2つ目は1から指定された整数値の範囲からランダムな整数値を選択する。3つ目はrandom.uniformメソッドを使って0から指定された浮動小数点数値までの範囲から浮動小数点数値を選択する(一様分布)。

 以下に実際に試している例を示す。

randomモジュールのコマンドラインインタフェースの使用例 randomモジュールのコマンドラインインタフェースの使用例

 文字列を1つだけ指定した場合には、その中から1文字が選ばれるのではなく、常にその文字列が選ばれるので注意してほしい。晩ごはんに何のお弁当を買おうかと悩むときにはこのコマンドラインインタフェースが役に立つかもしれない。

「Python最新情報キャッチアップ」のインデックス

Python最新情報キャッチアップ

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。