8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】Python3.14からは型アノテーションの遅延評価がデフォルトになる

Posted at

はじめに

従来、Pythonの型アノテーションは「型を定義するより前にアノテーションで使うと実行時にNameErrorが送出される」仕様でした。
Python3.14ではこれが解消され、定義順によってエラーが発生することはなくなります。
この変更により文字列アノテーション、ForwardRefTYPE_CHECKINGといった特別なテクニックを使わずに、型アノテーションを書くことができるようになります。

Python 3.14ではエラーにならなくなる書き方
def bar(foo: Foo): ...  # Python 3.13以前ではNameErrorが発生するためこの書き方ができない。
# そのため、
#
# def bar(foo: "Foo"): ...
#
# または、
# def bar(foo: ForwardRef["Foo"]): ...
#
# または、
# if TYPE_CHECKING:
#     def bar(foo: Foo): ...  # 型ヒント用の関数定義
# else:
#     def bar(foo):
#         # 実行時に呼び出される実装
#         ...
#
# にする必要がある

class Foo: ...  # 型アノテーションで使われるより後の行で型が定義されている

私はこの挙動が実現したのは、前方参照してもエラーを発生させなくするfrom __future__ import annotationsがデフォルト動作になったことによるものだと思っていました。
しかし、オブジェクトが保持している情報を直接取得したり、PEPやドキュメントを読み進めるうちに、annotationsをインポートした状態がデフォルトになったのではないことがわかってきました。
この記事では、Python 3.14で導入された新しいアノテーションの評価方法について詳しく解説します。

Python 3.13とPython 3.14の挙動の違い

__future__インポートを使用しない場合と使用する場合を比較してみます。

__future__なし
def bar(foo: Foo): ...


class Foo: ...


print(bar.__annotations__)
__future__あり
from __future__ import annotations


def bar(foo: Foo): ...


class Foo: ...


print(bar.__annotations__)

Python 3.13の挙動

Python 3.13環境で__future__なしのコードを実行すると、Fooがまだ定義されていないためNameErrorが発生します。

__future__なしのコードをPython 3.13環境で実行
$ python sample.py
Traceback (most recent call last):
  File ".../sample.py", line 1, in <module>
    def bar(foo: Foo): ...
                 ^^^
NameError: name 'Foo' is not defined

Python 3.13環境で__future__ありのコードを実行すると、__future__インポートによってアノテーションが文字列として評価されるため、エラーは発生しません。

__future__ありのコードをPython 3.13環境で実行した結果
$ python sample.py
{'foo': 'Foo'}

また、関数の__annotations__属性からは{引数名: 型名文字列}の辞書が取得できます。

Python 3.14の挙動

では、Python 3.14環境ではどうなるでしょうか。

__future__ありのコードをPython 3.14環境で実行した結果
$ python sample.py
{'foo': 'Foo'}

__future__ありのときはPython 3.13と同じく関数の__annotations__属性から{引数名: 型名文字列}の辞書が取得できます。

__future__なしのコードをPython 3.14環境で実行した結果
$ python sample.py
{'foo': <class '__main__.Foo'>}

__future__なしのときはPython 3.13とは違いエラーにならず、{引数名: クラス}となります。

これは、from __future__ import annotationsを行えばアノテーションされた型を文字列として評価するとしたPEP563の仕様とは異なります。

Python 3.14からはPEP563のデフォルト化とは別の仕様となったことが分かったので、調査してみました。

仕様変更についてのPEP

調査したところ、下記のPEPによってPython 3.14からのアノテーション評価の仕様が定められたことがわかりました。

PEP 649 - Deferred Evaluation Of Annotations Using Descriptors

これは アノテーションの遅延評価 (deferred evaluation) を導入するPEPです。
PEP563は前方参照問題を解決する手段として導入されましたが、実行時にアノテーション情報を利用するpydanticfastapiのようなライブラリとの相性が悪い状態でした。
包括的に問題を解決するため、アノテーションがアクセスされるまで評価を遅らせることがこのPEPで提案されました。
これにより、型ヒントを記述する際に、定義順序を気にする必要がなくなります。
そして、実行時の型アノテーション情報を取得する際は型名文字列ではなく、クラスそのものを取得するようになりました。

PEP 749 – Implementing PEP 649

これはPEP649のアノテーション遅延評価モデルの実装に伴い、様々な既存機能の変更や新規標準モジュール(annotationlib)の追加を行うPEPです。
その一つとして、from __future__ import annotationsは将来的に非推奨化のち廃止されます。これにより、__future__インポートの必要がなくなり、コードのシンプル化が図られます。

変化の概要

Pythonの型アノテーション評価方法がどのように変化していったかをまとめると、下記のようになります。

  1. Stock semantics:
    Python 3.13までのデフォルトの評価方法です。
    アノテーションはソースコード中に現れた時点で即座に評価("eagerly evaluated")されます。これにより、アノテーション内でまだ定義されていない名前を参照すると実行時にNameErrorが発生します。

  2. Stringized annotations:
    Python 3.7以降、from __future__ import annotationsを使用した際に適用できる評価方法です。
    アノテーションが文字列として評価されます。これにより、前方参照の問題は解決しましたが、実行時にアノテーション情報を取得する際に処理が複雑になるという新たな問題が生じました。

  3. Deferred evaluation:
    Python 3.14以降のデフォルトとなる評価方法です。
    アノテーションは、アクセスされたときに初めて遅延的に評価("lazily evaluated")されます。前方参照の問題を解決しつつ、実行時にアノテーション情報が取得しやすくなることを両立します。

まとめ

Python 3.14で導入された遅延評価は、型アノテーションの体験を大きく向上させるものです。
これまでクラスの定義順によっては特殊なテクニックが必要だった型ヒントは、より直感的で使いやすいものへと進化しています。
「型ヒントのために特別なことをしなくてもいいようにする」という点では、PEP695で型変数を定義しやすくなったことと同一方向である言語機能の改善だと考えています。

アノテーションの遅延評価に関する変更は、ユーザーの利便性を考えるPythonコミュニティの姿勢を反映した重要な一歩であり、動的型付けと静的型チェックの利点を高い次元で両立しようとする、Pythonの継続的な進化を示すものだと考えています。

8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?