ウェブサイト検索

Python 3.13: 試してみたいクールな新機能


Python 3.13 は 2024 年 10 月 7 日に公開されました。この新しいバージョンはこの言語にとって大きな前進ですが、いくつかの大きな変更は内部で進行しており、すぐには目に見えません。

ある意味、Python 3.13 は、特に言語のパフォーマンスに関して、将来のいくつかの改善のための基礎を築いています。読み進めていくと、この背景についてさらに学び、現在完全に利用できるようになったいくつかの新機能について詳しく説明します。

このチュートリアルでは、次のような新しいバージョンのいくつかの改善点について学びます。

  • 対話型インタープリター (REPL) が改善されました。
  • よくある間違いを修正するのに役立つ明確なエラー メッセージ
  • グローバル インタープリター ロック (GIL) の削除と Python のフリースレッド化の進歩
  • 実験的なジャストインタイム (JIT) コンパイラの実装
  • Python の静的型システムへの多数のマイナーアップグレード

このチュートリアルの例のいずれかを試したい場合は、Python 3.13 を使用する必要があります。チュートリアル「Python 3 インストールおよびセットアップ ガイド」および「Python のプレリリース バージョンをインストールするにはどうすればよいですか?」新しいバージョンの Python をシステムに追加するためのいくつかのオプションについて説明します。

この言語に追加される新機能について詳しく知ることに加えて、新しいバージョンにアップグレードする前に考慮すべきことについてのアドバイスも得られます。 Python 3.13 の新機能を示すコード例をダウンロードするには、以下のリンクをクリックしてください。

改良されたインタラクティブ インタプリタ (REPL)

スクリプトやコードを指定せずに Python を実行すると、Python の対話型インタープリターの内部にいることがわかります。このインタプリタは、読み取り、評価、印刷のループに基づいているため、非公式には REPL と呼ばれています。 REPL は入力を読み取り、 それを評価し、 結果を出力してからループして同じことを再度実行します。 。

Python REPL は何十年も前から存在しており、Python を初心者に優しい言語にする探索的なワークフローをサポートしています。残念ながら、インタプリタには、複数行の編集やコードの効率的な貼り付けなど、期待されるいくつかの機能がありません。

まず REPL を開始します。これを行うには、ターミナルに「python」と入力します。設定によっては、代わりに pypython3、さらには python3.13 を記述する必要がある場合があります。 Python 3.13 に同梱されている新しいインタープリターを使用していることを認識する 1 つの方法は、3 つの山形 (>>>) で構成されるプロンプトが微妙に色付けされていることです。

1 つの改善点は、REPL 固有のコマンドを、かっこを付けて呼び出すことなく、Python 関数であるかのように使用できることです。使用できるコマンドとキーボード ショートカットの一部を次に示します。

  • exit または quit: インタープリタを終了します。
  • clear: 画面をクリアします。
  • ヘルプ または F1: ヘルプ システムにアクセスします。
  • F2: 履歴ブラウザを開きます。
  • F3: 貼り付けモードに入ります

これらのオプションの詳細については、「Python 3.13 Preview: A Modern REPL」を参照してください。

Python 3.13 以前の REPL では、特に複数行にまたがるコード ブロックを扱う場合、以前に作成したコードを呼び出すのが面倒でした。従来は、 を繰り返し押して、各行を 1 行ずつ戻す必要がありました。 3.13 では、 キーを 1 回押すだけでコード ブロック全体を元に戻すことができます。

これを自分で試してみるには、REPL に次のコードを入力します。

>>> numbers = range(3, 13)
>>> [
...     (number - 3)**3 for number in numbers
...     if number % 2 == 1
... ]
[0, 8, 64, 216, 512]

ここでは、数値が奇数の場合に限り、数値範囲のオフセット 3 乗を計算する、やや複雑なリスト内包表記を作成しています。重要なのは、読みやすくするために、リストの内包表記を複数の行に分割していることです。 キーを押してみてください。インタプリタは 4 行すべてを一度に呼び出すので、矢印キーを使用して式内を移動し続けることができます。

コードを変更して、再度実行することができます。更新されたコードを実行するには、コード ブロックの最後の行の末尾にカーソルを移動する必要があります。式内で Enter を押すと、代わりに新しい空行が作成されます。

複数行のステートメントを呼び出して編集できる機能により、時間を大幅に節約でき、REPL を使用する際の作業効率が向上します。

Python 3.13 のもう 1 つの便利な点は、コードの貼り付けが適切にサポートされていることです。 Python 3.12 以前では、コードをコピーして貼り付ける前に、コードに空白行が含まれていないことを確認する必要があります。新しいバージョンでは、貼り付けたコードは 1 つのユニットとして扱われ、スクリプト内で実行されるのと同じように実行されます。

これにより、REPL を使用してスクリプトを対話的に調整およびデバッグすることがより便利になります。例として、プレイ中の卓上ゲームでサイコロとして機能するために、乱数を出力できるスクリプトを作成するとします。

import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
    roll = input()
    if roll.lower().startswith("q"):
        break
    if roll.isnumeric():
        num_faces = int(roll)

    result = random.randint(1, num_faces)
    print(f"Rolling a d{num_faces:<2d} -  {result:2d}")

このコードをコピーして古い REPL に貼り付けようとしても、機能しません。 while ループ内の空白行により問題が発生します。一見何も起こらないように見える無限ループに入ります。ループを停止するには、q と入力して Enter を押すか、Ctrl を押します。 <span>+C

Python 3.13 では、貼り付けは問題なく機能します。

>>> import random
...
... num_faces = 6
...
... print("Hit enter to roll die (q to quit, number for # of faces) ")
... while True:
...     roll = input()
...     if roll.lower().startswith("q"):
...         break
...     if roll.isnumeric():
...         num_faces = int(roll)
...
...     result = random.randint(1, num_faces)
...     print(f"Rolling a d{num_faces:<2d} -  {result:2d}")
...
Hit enter to roll die (q to quit, number for # of faces)
13
Rolling a d13 -  10

Rolling a d13 -   5

Rolling a d13 -   3
q

貼り付けたすべてのコードに対してプロンプトが 3 つのドット (...) として表示され、すべてが 1 つのコード単位の一部であることを示していることに注意してください。この例では、13 面体のサイコロであるd13を 3 回振ることを選択します。

Python 3.13 で Python 開発者のエクスペリエンスが少し向上するもう 1 つの分野は、エラー メッセージの改善です。これについては次に学習します。

エラーメッセージの改善

REPL は、Python を初めて使い始めるときに使用するのに最適です。また、すぐに遭遇するもう 1 つの機能は、Python のエラー メッセージです。エラー メッセージがあなたを助けようとしているように感じられることは必ずしもありません。ただし、ここ数回のリリースで、よりフレンドリーで便利になりました。

  • Python 3.10 では、多くのエラー メッセージが技術的ではなくなり、より正確になったため、改善されました。
  • Python 3.11 では、トレースバックにさらに多くの情報が追加され、問題の原因となっているコードを特定しやすくなりました。
  • Python 3.12 では、インポート エラーへの対処がより簡単になりました。

Python 3.13 は、開発者のエクスペリエンスを向上させるこの素晴らしい伝統を継承しています。このリリースでは、実行時エラーが発生したときに表示されるトレースバックに色が追加されます。さらに、より多くの種類のエラー メッセージが、エラーの修正方法に関する提案を提供します。このセクションではこれらについて詳しく見ていきます。

まず、Python を起動してランタイム エラーを作成し、色付きのトレースバックを確認します。 REPL を開き、次のように inverse() を定義します。

>>> def inverse(number):
...     return 1 / number
...

inverse() 関数は、数値の逆乗を計算します。 inverse() が考慮していない問題の 1 つは、ゼロには逆数がないということです。関数内で、 inverse(0)ZeroDivisionError を発生させるため、これが表示されます。

トレースバックに色が付き、重要な情報が赤と紫で強調表示されるようになりました。一般に、これによりエラー メッセージが読みやすく、理解しやすくなります。

ただし、色が気に入らない場合は、PYTHON_COLORS 環境変数を 0 に設定して色をオフにすることができます。

Python 3.10 以降、エラー メッセージには、キーワード、関数名、さらにはモジュール名のスペルを間違えた場合に役立つもしかしてという提案機能が含まれています。 Python 3.13 では、関数呼び出しにキーワード引数を含めるように提案が拡張されました。数値のリストを降順に並べ替えようとしているとします。

>>> numbers = [2, 0, 2, 4, 1, 0, 0, 1]
>>> sorted(numbers, reversed=True)
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    sorted(numbers, reversed=True)
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: sort() got an unexpected keyword argument 'reversed'.
           Did you mean 'reverse'?

>>> sorted(numbers, reverse=True)
[4, 2, 2, 1, 1, 0, 0, 0]

reverse=True を使用して数値を逆順に並べ替えようとします。最初にタイプミスをして、最後に d を付けて引数 reversed を呼び出します。 Python はこれに気づき、reverse の意味であることを有益に示唆します。

Python の高度な機能の一部は、単純なメカニズムによって支えられています。たとえば、独自の Python モジュールを作成するには、.py という接尾辞が付いた名前のファイルを作成するだけです。これは言語を探求するのに最適ですが、興味深い方向に物事がうまくいかない可能性への扉も開きます。

次のシナリオは、おそらく遭遇したことがあるシナリオです。先ほどのサイコロを振るスクリプトを思い出してください。代わりにファイル random.py を呼び出したとします。

import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
    roll = input()
    if roll.lower().startswith("q"):
        break
    if roll.isnumeric():
        num_faces = int(roll)

    result = random.randint(1, num_faces)
    print(f"Rolling a d{num_faces:<2d} -  {result:2d}")

ゲームがテーブルに用意されており、サイコロを振り始めるのが楽しみです。スクリプトを実行して、ランダム性を展開します。

$ python random.py
Hit enter to roll die (q to quit, number for # of faces)

Traceback (most recent call last):
  File "/home/realpython/random.py", line 1, in <module>
    import random
  File "/home/realpython/random.py", line 13, in <module>
    result = random.randint(1, num_faces)
             ^^^^^^^^^^^^^^
AttributeError: module 'random' has no attribute 'randint' (consider
                renaming '/home/realpython/random.py' since it has the same
                name as the standard library module named 'random' and the
                import system gives it precedence)

モジュール「random」には属性「randint」がありませんなどのエラー メッセージは、数十年にわたって Python 開発者を混乱させてきました。確かに、randint()random モジュールの関数の 1 つです。

Python 3.13 では、エラー メッセージが問題の可能性を指摘するため、頭を悩ませる必要はもうありません。 random.py を作成したときに、標準ライブラリのモジュールをシャドウする新しい random モジュールを作成しました。この問題を解決するには、スクリプトの名前を変更する必要があります。

random.py の名前を roll_dice.py に戻し、再度実行します。

$ python roll_dice.py
Hit enter to roll die (q to quit, number for # of faces)

Rolling a d6  -   3

Rolling a d6  -   1

Rolling a d6  -   3
20
Rolling a d20 -  13
q

スクリプトは標準ライブラリから random を隠蔽しなくなりました。上の例では、最初に通常の 6 面サイコロを 3 回振ってから、20 面サイコロに切り替えます。

サードパーティのパッケージをシャドウイングした場合も、同様のエラー メッセージが表示されます。一般に、トップレベルの Python ファイルには、インポートするライブラリと同じ名前を付けるように注意する必要があります。

開発者エクスペリエンスを向上させるための継続的な取り組みは、Python が初心者に優しい言語としての地位を守るのに役立ちます。

フリースレッド Python: 見てください、GIL はありません

Python のような高級言語は、開発者にとっていくつかの利便性を提供します。たとえば、Python がメモリを処理してくれると信頼できます。データ構造を初期化する前にメモリの割り当てについて心配する必要はありません。また、作業が終了したときにメモリを解放することを忘れる必要もありません。

Python が役立つもう 1 つのプログラミングの問題は、コードがスレッドセーフであることを確認することです。簡単に言えば、コードをスレッドセーフにするためには、2 つの異なる実行スレッドがメモリの同じ部分を同時に更新しないようにする必要があります。

これに対する Python の解決策は少し強引です。グローバル インタープリタ ロック (GIL) を使用して、一度に 1 つのスレッドだけがメモリにアクセスできるようにします。ほとんどの操作では、スレッドは最初に GIL を取得する必要があります。このロックはグローバルであるため (GIL が 1 つだけあるため)、ほとんどの Python プログラムは、複数の CPU が利用可能な最新のハードウェアで実行されている場合でも、実質的にシングルスレッドになります。

歴史的に、GIL は、スレッドセーフではない C ライブラリにバインディングを追加するためのシンプルで効果的なソリューションであることが証明されています。 GIL は、プログラミング言語としての Python の人気の高まりに重要な役割を果たしたと考えられます。

コンピューターで使用できる CPU の数が年々増加するにつれて、GIL は英雄というよりもトラブルメーカーになりました。コミュニティは言語から GIL を削除することを何度か試みてきました。サム・グロス氏によって始められた最新の試みは、これまでで最も有望である。実際、グローバル インタープリタ ロックのない特別なフリースレッドバージョンの Python 3.13 をセットアップできます。

Python のフリースレッド バージョンを試すには、ある程度の時間と労力が必要です。プラットフォームの通常の配布チャネルからバージョンを入手できる場合があります。そうでない場合は、Python のソース コードをダウンロードして自分でビルドするか、Docker を通じて Python を実行できます。詳細については、「無料のスレッド プレビューの新機能を実際に使ってみよう」を参照してください。

フリースレッドの Python 実行可能ファイルは通常、t というサフィックスが付いた python3.13t という名前が付けられます。 Python のフリースレッド バージョンを入手したら、Python を実行するたびに GIL を有効にするか無効にするかを選択できます。

$ CODE='import pyfeatures; print(pyfeatures.FreeThreading())'

$ python3.13 -c "$CODE"
Free Threading: unsupported

$ python3.13t -c "$CODE"
Free Threading: enabled ✨

$ python3.13t -X gil=1 -c "$CODE"
Free Threading: disabled

-X gil=1 を設定することで、GIL を有効にし、フリー スレッドをオフにします。上記のコードを実行するために必要な pyfeatures モジュールは、ダウンロード可能な資料から入手できます。これには、空きスレッドのステータスを検出する機能が含まれています。

フリースレッド バージョンで GIL を有効にすることは、Python の標準バージョンを実行することと完全に同等ではないことに注意してください。少なくとも Python 3.13 の初期バージョンでは、GIL を有効にしてフリースレッド バージョンを実行すると、パフォーマンスにいくつかの問題が発生します。

この特定のベンチマークは、4 つの CPU を搭載したコンピューターで実行されます。 GIL を無効にしたフリースレッド Python のグラフは、最初の 4 つのスレッドではほぼ平坦であることに注意してください。これは、このバージョンの Python がコンピューター上のすべてのコアを利用できることを示しているため、非常に有望です。

このベンチマークについて詳しく知りたい場合は、フリースレッド Python のパフォーマンスについてさらに詳しく説明されている「Python 3.13 のパフォーマンス向上の測定」を読んでください。

GIL の削除は大規模な作業であり、将来的には Python のパフォーマンスが向上することが期待されます。次のセクションでは、Python の高速化を目的とした別の機能について説明します。

実験的な JIT コンパイラ

Python はインタプリタ型言語です。これは、すべてコンパイル言語である C や Rust のような言語とは対照的です。インタープリター型言語では、コードを実行するために Python インタープリターのようなランタイムが必要です。一方、コンパイルされたコードは通常、システム上で直接実行できます。

どちらのモデルにも長所と短所があります。 Python は開発者にとって効率的な言語であることが多く、新しいコードをすぐにテストできます。ただし、コード自体の実行速度は、同等のコンパイル済みコードよりも遅くなる可能性があります。

ジャストインタイム (JIT) コンパイラーは、一種の中間点を提供します。インタープリターは、プログラムの実行速度を上げるために、プログラムの実行中に一部のコードをコンパイルすることを選択できます。 Python 3.13 には、新しい実験的な JIT コンパイラーがあります。実験的であるということは、Python のソース コードには存在しますが、デフォルトでは有効になっていないことを意味します。

JIT コンパイラーを試したい場合は、JIT を有効にして特別なバージョンの Python 3.13 をセットアップする必要があります。これは、フリースレッド Python を試すために必要なことと似ていますが、ビルド フラグは異なります。

Python の JIT コンパイラは、コピー アンド パッチと呼ばれるアルゴリズムに基づいています。インタプリタは、プリコンパイルされたテンプレートと一致するコード内のパターンを検索し、変数のメモリ アドレスなどの特定の情報をマシン コードに埋め込みます。

JIT コンパイルは効果的であり、同じ操作が何度も繰り返されるコードのパフォーマンスが向上し、JIT コンパイラーにコードを分析して変換する時間を与えます。フィボナッチ数列を繰り返し計算したあるベンチマークでは、JIT によるパフォーマンスのわずかな向上が見られます。

このベンチマークでは、JIT が有効になっている Python 3.13 は、Python 3.12 および通常の Python 3.13 よりも高速です。ただし、JIT はまだ日常的に使用できる状態ではないため、試してみる程度に留めてください。 JIT が将来の Python リリースにさらに大きな影響を与えることを願っています。

上記のベンチマークと新しい実験的な JIT コンパイラーの詳細については、「Python 3.13 プレビュー: フリー スレッディングと JIT コンパイラー」をご覧ください。

静的型付けの改善

Python は動的に型指定される言語ですが、オプションの型ヒントに静的な型情報を追加できます。 mypy や Pyright などの別の型チェッカーを実行してコードを検証することも、IDE を利用して問題についてアドバイスすることもできます。

Python の静的型システムの基礎は PEP 484 で定義され、Python 3.5 で導入されました。 2023 年 11 月、PEP 729 はタイピング評議会を設立し、タイピング仕様を通じて型システムを正式化しました。

Typing Council は Python 型システムを管理し、それを便利で使いやすく、安定したものにすることを目指しています。タイピング仕様を維持し、タイピング関連の PEP について Python 運営評議会に助言します。タイピング評議会の初期メンバーは次のとおりです。

  • レベッカ・チェン
  • シャンタヌ・ジャイナ教
  • カール・マイヤー
  • エリック・トラウト
  • ジェレ・ザイルストラ

Python の型システムのガバナンスをより明確にすることに加えて、コミュニティはコード関連の変更もいくつか実装しました。次の PEP が Python 3.13 で受け入れられています。

  • PEP 696: 型パラメータの型デフォルト
  • PEP 742: TypeIs による型の絞り込み
  • PEP 705: TypedDict: 読み取り専用アイテム
  • PEP 702: 型システムを使用した非推奨のマーク付け

このチュートリアルでは、最初の 2 つの変更点、つまり型のデフォルトと型の絞り込みの改善に焦点を当てます。読み取り専用の TypedDict 項目や非推奨について詳しく知りたい場合は、それぞれの PEP を参照するか、ダウンロード可能な資料を確認してください。後者には、自分で試してみることができる例が含まれています。

この言語の他の機能と同様に、入力の改善は Python の特定のバージョンに関連付けられています。ただし、使用している型チェッカーが新しい機能を実装していることも必要です。可能な場合、新機能は typing_extensions ライブラリを通じて古い Python バージョンにバックポートされます。たとえば、typing の代わりに typing_extensions からインポートすることで、たとえば Python 3.10 で TypeIs を使用できます。

(venv) $ python -m pip install pyright

Visual Studio Code を使用している場合は、Pylance 拡張機能を介してエディター内で Pyright を使用できます。設定で [Python › 分析: 型チェック モード] オプションを設定して有効にする必要がある場合があります。

Python 3.12 では、型ジェネリックスの新しい構文が導入されました。 ジェネリック型は、別の型によってパラメータ化された型です。たとえば、リストは文字列のリストまたは整数のリストを持つことができるため、ジェネリック型です。どちらもリストですが、文字列と整数という異なる型によってパラメータ化されます。

従来は、TypeVar を使用して型変数を明示的に定義する必要がありました。 Python 3.12 以降、角括弧を使用して、よりコンパクトな [T] 構文で型変数を宣言できます。

from collections import deque

class Queue[T]:
    def __init__(self) -> None:
        self.elements: deque[T] = deque()

    def push(self, element: T) -> None:
        self.elements.append(element)

    def pop(self) -> T:
        return self.elements.popleft()

この例では、deque に基づいて汎用キューを作成します。キューは次のように使用できます。

>>> from generic_queue import Queue

>>> string_queue = Queue[str]()
>>> string_queue.push("three")
>>> string_queue.push("thirteen")

>>> integer_queue = Queue[int]()
>>> integer_queue.push(3)
>>> integer_queue.push(13)

インスタンスの作成時に角括弧内に [str] を追加することで、string_queue が文字列要素を含むキューになることを宣言します。同様に、[int] は、integer_queue が整数要素を持つことを指定します。角括弧を省略して Queue() を直接呼び出すと、キューに任意の型の要素を含めることができます。

このような種類のジェネリック クラスの型のデフォルトを指定できるようになりました。型を明示的に定義しない場合は、このデフォルトが想定されます。

from collections import deque

class Queue[T=str]:
    def __init__(self) -> None:
        self.elements: deque[T] = deque()

    def push(self, element: T) -> None:
        self.elements.append(element)

    def pop(self) -> T:
        return self.elements.popleft()

string_queue = Queue()
reveal_type(string_queue)

integer_queue = Queue[int]()
reveal_type(integer_queue)

関数のデフォルト引数を指定する方法と同様に、型のデフォルトを定義します。構文 [T=str] は、T の型が指定されていない場合は str になることを意味します。これを確認するには、特別な reveal_type() を使用します。この関数は型チェッカーによって理解されますが、コードを実行すると失敗します。

$ pyright --pythonversion 3.13 generic_queue.py

generic_queue.py
  generic_queue.py:15:13 - information: Type of "string_queue" is "Queue[str]"
  generic_queue.py:18:13 - information: Type of "integer_queue" is "Queue[int]"
0 errors, 0 warnings, 2 informations

コードで Pyright を実行すると、明示的に指定していない場合でも、reveal_type() によって string_queue が文字列のキューであることが確認されます。

型のデフォルトは、ジェネリックの操作をより便利にする小さな追加です。

Python 3.10 では型ガードが導入されました。これらにより、共用体の型を絞り込むことができます。再帰的に定義されたツリー構造の次の例を考えてみましょう。

from typing import TypeGuard

type Tree = list[Tree | int]

def is_tree(obj: object) -> TypeGuard[Tree]:
    return isinstance(obj, list) and all(
        is_tree(elem) or isinstance(elem, int) for elem in obj
    )

def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
    if is_tree(tree_or_leaf):
        return get_left_leaf_value(tree_or_leaf[0])
    else:
        return tree_or_leaf

ここで、is_tree() は、ツリーを識別できる型ガードとして機能します。コード内の Tree は、ネストされた整数のリストです。再帰的な get_left_leaf_value() 関数は、Tree または単一の整数のいずれかを受け取ります。ただし、is_tree() をチェックした後は、12 行目のツリーの最初の要素に安全にアクセスできます。

型ガードには、ジェネリック型ではうまく機能せず、肯定的な保証のみを提供するといういくつかの制限があります。後者は、型ガードが False を返した場合、型チェッカーは型の絞り込みを行うことができないことを意味します。

上記の例では、これは、型チェッカーの場合、14 行目の tree_or_leaf が依然として Tree | 型であることを意味します。 int:

$ pyright --pythonversion 3.13 tree.py

tree.py
  tree.py:14:16 - error: Type "Tree | int" isn't assignable to return type "int"
    Type "Tree | int" is not assignable to type "int"
      "list[Tree | int]" is not assignable to "int" (reportReturnType)
1 error, 0 warnings, 0 informations

ここで、型チェッカーは 2 番目の return ステートメントの型について警告します。ただし、これは else ブランチ内にあるため、tree_or_leafint であることがわかります。型チェッカーにこれを認識させるには、整数用の別の型ガードが必要になります。

TypeIsTypeGuard に似ていますが、より多くの型絞り込み情報を型チェッカーに提供し、上記の例の問題を解決します。多くの場合、TypeIsTypeGuard のドロップイン置換として使用できます。

from typing import TypeIs

type Tree = list[Tree | int]

def is_tree(obj: object) -> TypeIs[Tree]:
    return isinstance(obj, list) and all(
        is_tree(elem) or isinstance(elem, int) for elem in obj
    )

def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
    if is_tree(tree_or_leaf):
        return get_left_leaf_value(tree_or_leaf[0])
    else:
        return tree_or_leaf

TypeGuardTypeIs に置き換えました。これにより、型チェッカーにさらに多くの情報が提供されます。特に、13 行目で else 分岐に入ると、tree_or_leafTree になり得ないことがわかっているため、tree_or_leaf この場合、 は整数である必要があります。これは、Pyright を実行することで確認できます。

$ pyright --pythonversion 3.13 tree.py

0 errors, 0 warnings, 0 informations

型の絞り込みと、型付け仕様の TypeGuardTypeIs の違いについて詳しく読むことができます。

Python の型システムは常に進化していますが、新機能のほとんどは実際には既存の機能を段階的に改善したものです。これは、Python の静的型付けが現時点でかなり成熟していることを反映しています。

その他の非常に優れた機能

Python 3.13 で今後追加される機能のハイライトを見てきました。ただし、Python の新しいリリースには、熱心な開発者によって貢献された小さな変更が大量に含まれています。このセクションでは、必ずしも大きな見出しにならない改善点をいくつか確認します。

ランダムなコマンドライン

Python にはいくつかのツールが組み込まれています。より有名なもののいくつかは、json.toolhttp.server です。これらには、Python の -m スイッチを介してアクセスします。

$ python -m json.tool dog_friend.json
{
    "name": "Frieda",
    "age": 8
}

$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

json.tool を使用すると、JSON ファイルをフォーマットして読みやすくすることができます。 http.server を使用すると、ローカル HTTP サーバーをすばやく実行できます。

Python 3.13 では、コマンドライン インターフェイスが random モジュールに追加されています。

$ python -m random
usage: random.py [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]

positional arguments:
  input                 if no options given, output depends on the input
                            string or multiple: same as --choice
                            integer: same as --integer
                            float: same as --float

options:
  -h, --help            show this help message and exit
  -c, --choice CHOICE [CHOICE ...]
                        print a random choice
  -i, --integer N       print a random integer between 1 and N inclusive
  -f, --float N         print a random floating-point number between 0 and N

python -m random を呼び出すことで、乱数をすばやく作成したり、ランダムな選択を行ったりすることができます。入力に応じて、次の 3 つの結果のいずれかが返されます。

  • 要素のリストを渡すと、リストからランダムな要素が 1 つ取得されます。
  • 整数 N を渡すと、1 から N までのランダムな整数が得られます。
  • float x を渡すと、0 から x までのランダムな float が得られます。

自分で試してみてください:

$ python -m random Oslo Berlin London Krakow Belgrade Graz
Krakow

$ python -m random 6
3

$ python -m random 6.0
3.132801224231027

次のランダムな休暇を計画したい場合、サイコロの出目をシミュレートしたい場合、または単に乱数が必要な場合は、random モジュールを使用して簡単なヘルプを得ることができます。デフォルトでは、取得するランダムな結果の種類は渡した内容によって異なります。ただし、--choice--integer などのオプションを使用することもできます。 >--float を明示的に示します。

$ python -m random --float 6
1.4284991504686064

ここでは、--float を指定したため、制限として整数を渡しても、ランダムな浮動小数点数が得られます。このコマンドライン インターフェイスは高度なものではありませんが、ランダムな何かが必要な場合にすぐに役立ちます。

不変オブジェクトを変更するための新しい copy.replace()

不変オブジェクトを操作することにはいくつかの利点があります。不変オブジェクトは作成後に変更できないため、その状態を推論するのは簡単です。不変オブジェクトを簡単にキャッシュしたり、分散コンテキストで使用したりすることもできます。

Python には、タプル、フローズン セット、フローズン データ クラスなど、いくつかの不変のデータ構造が付属しています。データを不変のデータ構造で表す場合でも、元のオブジェクトと 1 つ以上のフィールドを共有する新しい不変オブジェクトを作成することで、データの状態を変更できます。

個人とその現在の Python バージョンをモデル化した次の例を考えてみましょう。

>>> from typing import NamedTuple

>>> class Person(NamedTuple):
...     name: str
...     place: str
...     version: str
...

>>> person = Person(name="Geir Arne", place="Oslo", version="3.12")

ここで、あなたの person が最新の Python バージョンにアップグレードするとします。 person が可変であった場合は、person.version="3.13" と書くことができます。ただし、名前付きタプルではそれは不可能です。代わりに、新しい person オブジェクトを作成し、 person がその新しいオブジェクトを指すようにします。

>>> person = Person(name=person.name, place=person.place, version="3.13")
>>> person
Person(name='Geir Arne', place='Oslo', version='3.13')

以前と同じ名前と場所で新しい person オブジェクトを作成しますが、Python のバージョンを更新します。これは機能しますが、データ構造に多くのフィールドがある場合、読み取り、書き込み、保守が困難になる可能性があります。

一部の組み込みの不変クラスは、専用のメソッドでこの使用例をサポートしています。たとえば、date オブジェクトと datetime オブジェクトをコピーし、その際に 1 つ以上のフィールドを置き換えることができます。

>>> from datetime import date

>>> today = date.today()
>>> today
datetime.date(2024, 9, 30)

>>> today.replace(day=1)
datetime.date(2024, 9, 1)

>>> today.replace(month=12, day=24)
datetime.date(2024, 12, 24)

.replace() を使用して、今月の最初の日、つまり今年のクリスマス イブを計算します。

Python 3.13 では、新しい replace() 関数が copy モジュールに追加され、多くの不変データ構造に同じ機能を一貫して提供します。 copy.replace() を使用して、上記の例を再作成できます。

>>> import copy

>>> person = Person(name="Geir Arne", place="Oslo", version="3.12")
>>> copy.replace(person, version="3.13")
Person(name='Geir Arne', place='Oslo', version='3.13')

>>> copy.replace(today, day=1)
datetime.date(2024, 9, 1)

replace() を使用してフィールドに新しい値を指定しない場合、元の値が保持されます。 Python バージョンをアップグレードするときは、version のみを指定する必要があり、他のフィールドは指定しません。前と同様、元のオブジェクトは変更されません。

>>> person
Person(name='Geir Arne', place='Oslo', version='3.12')

それが必要な場合は、person を明示的に更新する必要があります。

replace() を使用すると、名前付きタプルに加えてデータ クラスを変更できます。 replace() をカスタム クラスで動作させたい場合は、.__replace__() 特別メソッドを追加することもできます。これがどのように機能するかの例は、ダウンロード可能な資料にあります。

ファイルとディレクトリのグロビングの改善

Python の pathlib モジュールには、オペレーティング システム間で一貫してパスとファイルを操作するためのツールが多数含まれています。たとえば、.glob() を使用してファイルとディレクトリを一覧表示できます。

>>> from pathlib import Path

>>> Path("music")
PosixPath('music')

>>> for path in Path("music").glob("*"):
...     print(path)
...
music/opera
music/rap

この例では、コンピューター上の music/ ディレクトリを探索します。これには、operarap という 2 つの項目が含まれています。

自分で学習したい場合は、ダウンロード可能な資料の中に music/ ディレクトリがあります。

.glob() メソッドは、1970 年代以来ファイル名のセットを表すために使用されてきた特別な glob パターンを使用します。 *? などの特殊なワイルドカード文字を使用します。* は任意の数の任意の文字に一致し、? は任意の数の任意の文字に一致します。単一の文字。

たとえば、*.py は、.py で終わるファイル名と一致します。同様に、python3.1? は、python3.12python3.13python3.1X などの名前と一致します。ただし、python3.1python3.100 ではありません。

特別なパターンの 1 つは、多くの端末シェルで使用できるグロブスター (**) です。すべてのファイルとディレクトリを再帰的に照合します。 ls を使用して、Bash で music/ を探索できます。

$ ls music/**
music/opera:
flower_duet.txt  habanera.txt  nabucco.txt

music/rap:
bedlam_13-13.txt  fight_the_power.txt

ls music/** を使用すると、music/ 内のすべてのディレクトリとファイルが一覧表示されます。 Python は ** をサポートしていますが、その動作は他のツールと一貫性がありませんでした。 Python 3.12 では、次のことが確認できます。

>>> for path in Path("music").glob("**"):
...     print(path)
...
music
music/opera
music/rap

>>> for path in Path("music").glob("**/*"):
...     print(path)
...
music/opera
music/rap
music/opera/nabucco.txt
music/opera/flower_duet.txt
music/opera/habanera.txt
music/rap/bedlam_13-13.txt
music/rap/fight_the_power.txt

グロブスターを使用すると、ディレクトリのみがリストされます。ディレクトリとファイルの両方を一覧表示する場合は、拡張 **/* パターンを使用する必要があります。

Python 3.13 では、従来のツールと一貫性を保つために ** の動作が変更されました。

>>> for path in Path("music").glob("**"):
...     print(path)
...
music
music/opera
music/rap
music/rap/bedlam_13-13.txt
music/rap/fight_the_power.txt
music/opera/nabucco.txt
music/opera/flower_duet.txt
music/opera/habanera.txt

** を glob パターンとして使用すると、ディレクトリとファイルの両方がリストされるようになります。ディレクトリのみを表示するようにパターンを制限する必要がある場合は、明示的にパターンの最後にスラッシュ (/) を追加します。

>>> for path in Path("music").glob("**/"):
...     print(path)
...
music
music/opera
music/rap

.glob() がディレクトリのみを返すようにするには、パターンの最後にスラッシュ (/) を追加します。

通常は、pathlib を通じて .glob() を使用します。ただし、この機能は glob モジュールでも利用できます。 Python 3.13 では、glob パターンを正規表現 (regex) に変換するために使用できる新しい関数 glob.translate() が追加されています。

正規表現は、一般的な文字列の検索と一致に使用できるパターンです。これらは glob パターンよりも一般的で強力ですが、より複雑でもあります。 glob パターンを操作するときにさらに柔軟性が必要な場合は、それを正規表現に変換してみることができます。

>>> import glob

>>> pattern = glob.translate("music/**/*.txt")
>>> pattern
'(?s:music/(?!\\.)[^/]*/(?!\\.)[^/]*\\.txt)\\Z'

music/ 内にネストされている任意の .txt ファイルと一致する glob パターンを正規表現に変換します。次に、re.match() を使用して、正規表現パターンを任意の文字列と照合できます。

>>> import re

>>> re.match(pattern, "music/opera/flower_duet.txt")
<re.Match object; span=(0, 27), match='music/opera/flower_duet.txt'>

>>> re.match(pattern, "music/progressive_rock/")

>>> re.match(pattern, "music/progressive_rock/fandango.txt")
<re.Match object; span=(0, 35), match='music/progressive_rock/fandango.txt'>

この例では、2 つのファイル パスはパターンに一致しますが、ディレクトリ パスは一致しません。

Pathlib は、いくつかの操作で内部的に glob.translate() を使用するようになりました。これにより、ディレクトリの移動が高速になり、Path オブジェクトを小さくできるため、パフォーマンスが若干向上します。

裸のドキュメントストリング

Python はインライン ドキュメントを docstring に保存します。これらにはヘルプ システムからアクセスできます。実行時に、.__doc__ を通じて docstring にアクセスすることもできます。

>>> import dataclasses

>>> @dataclasses.dataclass
... class Person:
...     """Model a person with a name, location, and Python version."""
...     name: str
...     place: str
...     version: str
...
>>> Person.__doc__
'Model a person with a name, location, and Python version.'

class ステートメントの直後にテキスト文字列を記述することで、docstring を person に追加しました。 docstring には三重引用符で囲まれた文字列を使用するのが慣例です。 .__doc__ 特殊属性を調べることで、docstring にアクセスできます。

docstring は通常、関数、メソッド、またはクラス定義内に記述されるため、かなりの量のスペースが含まれます。 Python が必要とするインデントは docstring に反映されます。 Python 3.12 では、次のようになります。

>>> import dataclasses

>>> dataclasses.replace.__doc__
'Return a new object replacing specified fields with new values.\n\n    This is
 especially useful for frozen classes.  Example usage::\n\n
       @dataclass(frozen=True)\n      class C:\n          x: int\n
           y: int\n\n      c = C(1, 2)\n      c1 = replace(c, x=3)\n
       assert c1.x == 3 and c1.y == 2\n    '

>>> len(dataclasses.replace.__doc__)
299

>>> print(dataclasses.replace.__doc__)
Return a new object replacing specified fields with new values.

    This is especially useful for frozen classes.  Example usage::

      @dataclass(frozen=True)
      class C:
          x: int
          y: int

      c = C(1, 2)
      c1 = replace(c, x=3)
      assert c1.x == 3 and c1.y == 2

最初の行を除く、docstring 内のすべての行には、先頭に 4 ~ 10 個のスペースが含まれることに注意してください。 Python 3.13 では、docstring が削除されます。これらは、共通の先頭の空白が削除された状態で保存されます。最新の Python バージョンで同じ例を再実行します。

>>> import dataclasses

>>> dataclasses.replace.__doc__
'Return a new object replacing specified fields with new values.\n\nThis is
 especially useful for frozen classes.  Example usage::\n\n
   @dataclass(frozen=True)\n  class C:\n      x: int\n      y: int\n\n
   c = C(1, 2)\n  c1 = replace(c, x=3)\n  assert c1.x == 3 and c1.y == 2\n'

>>> len(dataclasses.replace.__doc__)
263

>>> print(dataclasses.replace.__doc__)
Return a new object replacing specified fields with new values.

This is especially useful for frozen classes.  Example usage::

  @dataclass(frozen=True)
  class C:
      x: int
      y: int

  c = C(1, 2)
  c1 = replace(c, x=3)
  assert c1.x == 3 and c1.y == 2

この場合、最初の行を除くすべての行から 4 つのスペースが削除されています。これは小さな最適化であり、docstring が使用するメモリが以前よりも少なくなることを意味します。

.__doc__ の正確な値に依存するコードがある場合は、それをテストして、Python 3.13 でも期待どおりに動作することを確認する必要があります。

では、Python 3.13 にアップグレードする必要があるでしょうか?

Python 3.13 で追加される機能のいくつかを見てきました。 Python の変更ログには、さらに多くの内容が記載されています。このチュートリアルで説明されていない改善された機能には次のものがあります。

  • Argparse は、コマンド ライン アプリケーションの非推奨のオプション、引数、およびサブコマンドをサポートします。
  • locals() のセマンティクスを定義し、ローカル名前空間を参照する際の一貫性を確保します。
  • Python 3.11 で非推奨となった切れたバッテリーは削除されました。
  • iOS と Android は、ティア 3 レベルで Python のサポート対象プラットフォームになりました。

さらに、Python の年間リリース サイクルが改訂され、完全サポートが 18 か月から丸 2 年間に延長されました。各リリースには引き続き 5 年間セキュリティ修正が適用されます。

この図は、Python 3.13 と今後の 3.14 および 3.15 のライフサイクルを示しています。

では、これらの新しい改善点をすべて確認した上で、Python 3.13 にアップグレードする必要があるでしょうか?すべてのリリースと同様に、答えは明確で大声であり、それは状況によるです。

言語を探索するために使用するローカル環境を更新しない大きな理由はありません。新しい REPL と改善されたエラー メッセージは、生活の質を向上させる優れたアップグレードであり、パフォーマンスが若干改善される可能性があります。とはいえ、組み込みの REPL を使用しない場合は、すぐに利用する必要がある非常に魅力的な機能もありません。

特に Python の C-API を使用するライブラリを保守している場合は、通常バージョンとフリースレッドの両方の Python 3.13 でテストを開始する必要があります。実装内のバグをできるだけ早く表面化することが重要です。バグレポートの追加は、言語への大きな貢献です。フリー スレッドと互換性を持たせるためにライブラリの更新が必要になる場合もあります。

コードが実稼働環境で実行されている場合は、ランタイムをアップグレードする前に、Python 3.13 でコードをこれまでと同様に熱心にテストする必要があります。 Python 3.13 が 2024 年 5 月にベータ版になって以来、多くのテストが行われてきましたが、正式リリース後に表面化するバグは常にあります。重要なコードについては、2025 年 4 月に完全サポートが終了するまで Python 3.12 で実行しても問題ありません。

Python をいつアップグレードするかとは関係なく、いつ新しい機能を使い始めるべきかという問題があります。繰り返しますが、それはコードと状況によって異なります。

今回は、f-strings、セイウチ演算子、以前の matchcase ステートメントなど、構文に大きな変更はありません。代わりに、copy.replace()glob.translate() など、Python 3.13 でのみ使用できる関数がいくつかあります。

実行環境を制御できるアプリケーションに取り組んでいる場合は、環境をアップグレードするとすぐに Python 3.13 の機能を使用できます。チームで作業している場合は、互換性のない構文を導入する前に、全員が参加し、アップグレードされていることを確認する必要があります。

他の人が使用するライブラリを作成する場合、その人がどのバージョンの Python を使用するかわかりません。この場合、より保守的であり、理想的には各バージョンを存続期間中サポートすることが有益です。 Python 3.8 は 2024 年 10 月にサポート終了となるため、最小バージョンとして Python 3.9 を必須にすることを検討し始めることができます。

今後、Python 3.13 のバグ修正リリースが行われる予定です。およそ2か月ごとにリリースされる予定です。 Python 3.13 の使用を開始する場合は、バグ修正アップデートを常に最新の状態に保ち、利用可能になったら環境をアップグレードする必要があります。

結論

新しい Python のリリースは常に素晴らしいニュースです。 Python 3.13 で更新された機能と改善点について説明してきました。開発に時間と労力を費やしたすべてのボランティアに感謝の意を表します。

このチュートリアルでは、次のような新機能と改善点を確認しました。

  • 対話型インタープリター (REPL) が改善されました。
  • よくある間違いを修正するのに役立つ明確なエラー メッセージ
  • グローバル インタープリター ロック (GIL) の削除と Python のフリースレッド化の進歩
  • 実験的なジャストインタイム (JIT) コンパイラの実装
  • Python の静的型システムへの多数のマイナーアップグレード

おそらく新しい機能をすぐには利用できないでしょうが、システムに Python 3.13 をインストールして試してみてください。