Pythonの最新版 Python 3.11は、2022年10月に正式リリース予定です。
本記事では、Python3.11の更新内容を紹介します。
- Pythonのリリースについて
- Faster CPython
- Enhanced error locations in tracebacks
- PEP 655: Marking individual TypedDict items as required or potentially-missing.
- PEP 673: Self type.
- その他の変更点
Pythonのリリースについて
Pythonの最新版リリースは、PEPで正式版までのリリーススケジュールが公開され、概ねそのスケジュールに沿って開発が進められてます。
Python 3.11は2022年7月5日現在beta 3が公開されており、beta 4以降のスケジュールは PEP664 に下記のように記載されています。
- 3.11.0 beta 4: Thursday, 2022-06-16
- 3.11.0 beta 5: Saturday, 2022-07-09
- 3.11.0 candidate 1: Monday, 2022-08-01
- 3.11.0 candidate 2: Monday, 2022-09-05
- 3.11.0 final: Monday, 2022-10-03
candidateというのは正式版になる予定のバージョンのことです。
candidate後はバグフィックスのみで機能の変更は原則行われないので、candidateでも新機能を試すのには十分です。
PEP(Python Enhancement Proposals)
Pythonの機能やプロセス・環境などについて大きな変更があるときに書かれる提案書です。
PEPには、Pythonの機能が拡張される際の根拠や例となるコードも書かれています。
それでは、今回はPython 3.11の更新内容を4つ紹介します。
Faster CPython
Python 3.11はPython 3.10に比べて最大10-60%高速化されます。
pyperformance によるベンチマークで、 平均1.25 倍のスピードアップが計測されたそうです。
高速化のために既存のコードを修正する必要はありません。
Python 3.11にするだけで、速度が改善します。
Enhanced error locations in tracebacks
Python 3.11ではトレースバックが改善され、どこでエラーが発生したのか、より具体的な箇所が示されます。
たとえば、以下のような2点間の距離を軸毎に求めるプログラム(バグあり)を例に、実行結果をPython 3.10と比較してみましょう。
def distance(p1, p2): dx = abs(p1["x"] - p2["x"]) dy = abs(p1["y"] - p2["y"]) return {"x": dx, "y": dy} point_1 = {"x": 50, "y": 60} point_2 = {"x": 10, "z": 20} d = distance(point_1, point_2)
このプログラムをPython 3.10で実行すると、以下のようなメッセージが表示されます。
Traceback (most recent call last): File "/Users/pyq/scripts/distance.py", line 10, in <module> d = distance(point_1, point_2) File "/Users/pyq/scripts/distance.py", line 3, in distance dy = abs(p1["y"] - p2["y"]) KeyError: 'y'
10行目と3行目でエラーが発生した、ということが表示されています。
次にPython 3.11 で実行すると、以下のようなメッセージが表示されます。
File "/Users/pyq/scripts/distance.py", line 10, in <module> d = distance(point_1, point_2) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/pyq/scripts/distance.py", line 3, in distance dy = abs(p1["y"] - p2["y"]) ~~^^^^^ KeyError: 'y'
このように行だけではなく、どの位置でエラーが発生したのかも表示してくれるようになりました。
今回の場合は、p2["y"]
の箇所でKeyError
になったと表示されています。
そこでpoint_2
を確認してみると、辞書データの中に"y"
というキーがなかったことに気付きます。
エラーが発生したとき、よりデバッグしやすくなりますね。
PEP 655: Marking individual TypedDict items as required or potentially-missing.
Python 3.8で導入されたtyping
モジュールのTypedDict
を使うと、辞書のキーを固定できますが、キー毎に必須か・必須でないかの設定まではできませんでした。
Python 3.11ではTypedDict
のキー毎に、必須か・必須でないかを設定できるようになりました。
説明のために、「title」「year」という2つのキーを持ち「title」のみ必須、という辞書を考えてみたいと思います。
まずTypedDict
を使って、「title」「year」という2つのキーを持つ辞書型を定義すると、以下のように書けます。
from typing import TypedDict class Movie(TypedDict): title: str year: int
このMovie
を型ヒントに指定した場合、以下のように「title」「year」の両方をキーに持つ辞書であれば問題ありません。
# OK bttf: Movie = {"title": "バック・トゥ・ザ・フューチャー", "year": 1985}
しかし、TypedDict
はデフォルトで全項目が必須となるため、以下のように「year」がない辞書は型チェックでNGとなります。
# NG: yearがない bttf2: Movie = {"title": "バック・トゥ・ザ・フューチャー PART2"}
そこで、Python3.11から導入される NotRequired
を使い、Movie
を修正します。
from typing import TypedDict, NotRequired class Movie(TypedDict): title: str year: NotRequired[int]
これにより「year」が必須ではない項目となるため、以下のように書いても問題なくなります。
# OK: yearは必須ではない bttf2: Movie = {"title": "バック・トゥ・ザ・フューチャー PART2"}
TypedDict
がより使いやすくなりそうです。
PEP 673: Self type.
Python 3.11では、 typing
モジュールにSelf
が追加されます。
このSelf
を使うと、メソッドが自身のインスタンスを返す場合、よりシンプルに型ヒントを記述できます。
以下のクラスを例に説明します。
class Shape: def set_scale(self, scale: float) -> Shape: self.scale = scale return self class Circle(Shape): def set_radius(self, radius: float) -> Circle: self.radius = radius return self
このクラスを使い、以下のようなコードを書いてみます。
Circle().set_scale(0.5).set_radius(2.7)
これは、実行時には問題なく動作しますが、型チェックではエラーとなります。
Circle().set_scale(0.5)
でShape
クラスのインスタンスを返していることになっているため、.set_radius(2.7)
の部分で「Shape
にset_radius
という属性はない」というエラーになるのです。
この回避策として、これまでは TypeVar
を使って以下のように書くしかありませんでした。
from typing import TypeVar # Shapeとそのサブクラスを表す型変数 TShape = TypeVar("TShape", bound="Shape") class Shape: def set_scale(self: TShape, scale: float) -> TShape: self.scale = scale return self class Circle(Shape): def set_radius(self, radius: float) -> Circle: self.radius = radius return self
しかし、この書き方は冗長で、直感的でもありません。
そこで、Python 3.11ではSelf
が導入され、以下のように書けるようになりました。
from typing import Self class Shape: def set_scale(self, scale: float) -> Self: self.scale = scale return self class Circle(Shape): def set_radius(self, radius: float) -> Self: self.radius = radius return self
これにより、Shape().set_scale(0.5)
と書いた場合はShape
クラスのインスタンスを返すことを表し、Circle().set_scale(0.5)
と書いた場合はCircle
クラスのインスタンスを返すことを表せます。
自身のインスタンスを返すメソッドの型ヒントを、よりシンプルかつ直感的に書けますね。
その他の変更点
Python 3.11では今回紹介した4つ以外にも、さまざまな更新があります。
詳細は Python 3公式ドキュメント - What's New In Python 3.11 をご確認ください。