LoginSignup
148
91

More than 1 year has passed since last update.

Python3.11の新機能 (まとめ)

Last updated at Posted at 2021-12-31

はじめに

Python 3.5から What's Newの内容をまとめる記事を投稿してきました。

リリース前に次の新機能を先取り的に紹介を始めて今回で7回目です。その間にPythonもだいぶ進化して、6年前に比べて色々なところで使われるようになっていますが、まだまだ新たな機能が追加されていくようです。

次は3.11で、開発ロードマップ(PEP-664)はこの様になっています。

  • 3.11.0 開発開始: 2021-05-03 (完了)
  • 3.11.0 alpha 1: 2021-10-05 (完了)
  • 3.11.0 alpha 2: 2021-11-02 (完了)
  • 3.11.0 alpha 3: 2021-12-08 (完了)
  • 3.11.0 alpha 4: 2022-01-14 (完了)
  • 3.11.0 alpha 5: 2022-02-03 (完了)
  • 3.11.0 alpha 6: 2022-03-07 (完了)
  • 3.11.0 alpha 7: 2022-04-05 (完了)
  • 3.11.0 beta 1: 2022-05-08 (完了; これ以降は新たな機能の追加は行わない)
  • 3.11.0 beta 2: 2022-05-31 (完了)
  • 3.11.0 beta 3: 2022-06-01 (完了)
  • 3.11.0 beta 4: 2022-07-11 (完了)
  • 3.11.0 beta 5: 2022-07-26 (完了)
  • 3.11.0 candidate 1: 2022-08-08 (完了)
  • 3.11.0 candidate 2: 2022-09-12 (完了)
  • 3.11.0 final: 2022-10-24 (完了)

年に一度のリリースになったので去年のカレンダーとほぼ変わらないですね。ただ、beta2でpytestの非互換の問題が出てしまった(詳しくはこちら)関係で急遽beta3をリリースしたため、betaが1つ増えています。RCと最終リリースの日付は変わっていません。

そして、予定よりも3週間近く遅れましたが、10/24に 3.11.0が出ました。今回は性能改善も目玉機能の一つだったのですが、時間がなくてそれについて書けなかったのはとても残念でした。次回はもう少し自分の時間配分考えてちゃんと追いかけられるようにしたいですね。

更新履歴

2021-12-31

  • 最初のバージョン。a3が 2020-12-08にリリースされましたが、そのwhat's new をベースに書いています。

2022-07-31

  • 2022-07-26リリースのbeta5を元に書いています。
  • 以下を追加
    • PEP 646: 可変長ジェネリックス
    • PEP 655: TypedDictの個々のアイテムが必須かどうかを指定できるようになる
    • PEP 673: Self型
    • PEP 675: 文字列リテラル型
  • 「その他の言語の変更」を追加
  • 「新規に追加されたモジュール」を追加
  • 「モジュールの改善」に datetimepathlibを追加
  • 「廃止予定」に標準ライブラリからのモジュールの削除(PEP-594)を追加

2022-10-29

  • 2022-10-24に3.11.0がリリースされたので、開発ロードマップをアップデート。

注目した新しい機能

PEP 654: 例外グループと except*

ExceptionGroup という新しい例外型が定義されて、複数の例外を同時に上げたり、except* という記法によりそれを処理できるようになります。これまでも、例外処理中に例外が起きた時に複数の例外の情報を保持することは有りましたが、今回の変更はまったく関係のない例外をまとめるものです。用途として想定されているのが、例えば並行処理のライブラリで多数のタスクを同時実行中にその複数から例外が上がってくる場合などです。

そのような例外を ExceptionGroup を使うと以下のように上げることができます。

>>> try:
...     raise ExceptionGroup("eg", [ValueError(1), TypeError(2)])
... except ExceptionGroup as eg:
...     print(repr(eg))
...
ExceptionGroup('eg', [ValueError(1), TypeError(2)])

ExceptionGroupの1つ目の引数は名前で、任意の文字列が与えられます。そして2つ目の引数に例外のリストを渡します。ExceptionGroup 自身も例外型なので、以下のように入れ子にもできます。

>>> try:
...     raise ExceptionGroup("eg", [
...             ExceptionGroup("eg2", [
...                 ValueError(1),
...                 IOError(3),
...             ]),
...             TypeError(2)
...         ])
... except ExceptionGroup as eg:
...     print(repr(eg))
...
ExceptionGroup('eg', [ExceptionGroup('eg2', [ValueError(1), OSError(3)]), TypeError(2)])

そして、ExceptionGroupにはsubgroup()split()というメソッドがあって、条件(例えば 「TypeErrorだったら...」など)によってフィルタリングしたり二つのグループに分けたりできます。それを使って上記のように except ExceptionGroup...で例外処理書いても良いのですが、もっと楽な手段が提供されています。それが exception*という新たな記法です。

それを使うと、例外グループの例外処理がこの様に書けます。

try:
  ... # ExceptionGroupを上げる処理
except* ValueError:
  ... # ValueErrorの例外処理
except* TypeError:
  ... # TypeErrorの例外処理
except* IOError:
  ... # IOErrorの例外処理

PEP 657: トレースバックで例外の位置を正確に指し示す

例えば以下のようなコードを考えます。

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

def distance(a, b):
    return abs(a.x-b.x) + abs(a.y-b.y)

def main():
    a = Point(3, 4)
    b = None
    distance(a, b)

if __name__ == "__main__":
    main()

勘の良い方はお判りのように、このコードは distanceの中で例外を吐いて止まってしまいます。bという引数に Noneを与えているのでこれがxおよびyという属性を持たないからなのですが、3.10以前ではこのようなエラーメッセージが出ます。

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    main()
  File "test.py", line 14, in main
    distance(a, b)
  File "test.py", line 9, in distance
    return abs(a.x-b.x) + abs(a.y-b.y)
AttributeError: 'NoneType' object has no attribute 'x'

間違いではないのですが、これだと a.xのことなのかb.xの事なのかがわかりません。これが 3.11ではこのようになります。

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    main()
    ^^^^^^
  File "test.py", line 14, in main
    distance(a, b)
    ^^^^^^^^^^^^^^
  File "test.py", line 9, in distance
    return abs(a.x-b.x) + abs(a.y-b.y)
                   ^^^
AttributeError: 'NoneType' object has no attribute 'x'

ちょっとした工夫ですが、デバックが捗りそうです。

ただし、これのためにPythonのバイトコード(.pyc)に追加の情報が足されるのでバイトコードのサイズが少し大きくなり、実行時のメモリ使用量も少し増えます。それが嫌な場合は -X no-debug-rangesオプションを付けて実行するか、PYTHONNODEBUGRANGES環境変数を設定すると良いようです。

例外にメモを付与できる

BaseException__note__というフィールドが追加されて、例外に対して任意の文字列をセットできるようになりました。デフォルトではNoneで今までと変わらないけど、ここに文字列を入れると例外が起きた時にトレースバックで表示されます。例えばこんな感じで書けます。

e = ValueError()
e.__note__ = "こんにちは、こんにちは"
raise e

これを実行すると、こうなります。

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise e
    ^^^^^^^
ValueError
こんにちはこんにちは

んー、微妙だな。これ何のために必要なんだろうか。最大どれくらいの文字列をセットできるのかわからないけど、ライブラリの作成者が意図的に or 意図せずに、長い文字列を入れてしまい、それが起きた時にトレースバックが爆発しちゃうということが起こせちゃう。しかも例外が起きなければわからないのでテストで見つけにくい問題になる可能性がありますね。更に,トレースバックを入力にして動くツールとかに対して、悪さができちゃったりしないのかな。

PEP 646: 可変長ジェネリックス

PEP484で導入された TypeVarで1つの型でパラメータ化されたジェネリックスを定義できるようになりました。今回、TypeVarTuple が導入されて複数の型でパラメータ化されたジェネリックスを定義できるようになります。

これまでも、

T = TypeVar("T")
B = TypeVar("B")
S = TypeVar("S")

class WeirdTrio(Generic[T, B, S]):
  ...

w: WeirdTrio[int, str, float] = WeirdTrio(123, "xyz", 999.91)

と複数型によるパラメータ化が可能でしたが、明示的に羅列しなければならないので、任意の数の型ジェネリック型定義ができませんでした。これが、たとえば次のように書けるようになります。

Shape = TypeVarTuple("Shape")

class Array(Generic[*Shape]):
    def __init__(self, shape: tuple[*Shape]):
        self._shape: tuple[*Shape] = shape

    def get_shape(self) -> tuple[*Shape]:
        return self._shape

iarray: Array[int, int] = Array((480, 640)
farray: Array[float, float, float] = Array((-1.23, 3.45, -5.67))

NumPyやTensorFlowのArray型のShapeに関連するエラーを静的解析でキャッチできるようになりそう。

PEP 655: TypedDictの個々のアイテムが必須かどうかを指定できるようになる

typing.Requiredtyping.NotRequiredを使って、TypedDictの各アイテムが必須かどうかを指定できるよういになります。たとえば、

class Movie(TypedDict):
   title: str
   year: NotRequired[int]

とすると、titleは必須でyearはオプショナルになります。通常は何もつけないときは必須ですが、TypedDict継承時のパラメータで total=Falseを付けると、デフォルトがオプショナルになります。なので、以下の記述は上記と同等になります。

class Movie(TypedDict, total=False):
   title: Required[str]
   year: int

PEP 673: Self型

定義している型のインスタンスを返す時にこんな風に Self型が使えるようになります。

from typing import Self


class A:
    def __init__(self, value: int):
        self.value: int = value

    def copy(self) -> Self:
        return A(self.value)

copyの返り値の型として Aを指定したくなるのですが、Aはまだ定義途中なのでそれを参照することができなくてこれまで少し複雑なことをしなければなりませんでしたが、今後はSelfを使うことでラクができます。

PEP 675: 文字列リテラル型

typing.LiteralStringが導入されて、関数の呼び出しパラメータが文字列リテラル(定数)であることを求められるようになります。これは例えば、SQLやシェルのコマンドをパラメータとして文字列で渡す時に有効で、予めソースコードに埋め込まれている文字列だけを使えるようにして、インジェクション攻撃を防ぐことができます。

def run_query(sql: LiteralString) -> ...
    ...

def caller(
    arbitrary_string: str,
    query_string: LiteralString,
    table_name: LiteralString,
) -> None:
    run_query("SELECT * FROM students")       # ok
    run_query(query_string)                   # ok
    run_query("SELECT * FROM " + table_name)  # ok
    run_query(arbitrary_string)               # type checker error
    run_query(                                # type checker error
        f"SELECT * FROM students WHERE name = {arbitrary_string}"
    )

例を見るとすぐに理解できると思いますが、LiteralString型と定義されたパラーメータとそれらの連結はリテラル文字列ですが、任意のstr型の文字列あるいは任意文字列を埋め込んだfStringなどは型チェックエラーになります。

PEP 681: データクラス変換

クラスや関数が @typing.dataclass_transformデコレーターで装飾されると、静的型チェッカはそれが装飾するクラスを「データクラス風」に変換するものと認識します。それによって、データクラスを使っていない PydanticやSALAlchemyなどのライブラリもデータクラス同様に型チェックを行えるようになります。

PEP 563 アノテーションの遅延評価 → 無期限延期

  • python-3.10でデフォルトになるはずだったアノテーションの遅延評価はpython-3.11に延期されていましたが、Python-3.11でも無期限延期になりました。

その他の言語の変更

  • For文でStar表記 (*)が使えるようになりました。

    a = (1, 2, 3)
    b = (4, 5, 6)
    
    for x in *a, *b:
        print(x)
    1
    2
    3
    4
    5
    6
    

    それぞれのTupleを展開したものを連携してFor文で回してくれます。

    実はこれ、Python-3.9(2020年リリース)から使える機能で、PEGパーサーに切り替えたことによる副作用だったみたいです。で、今年になって「これできるようになっているんだけど、これっていいんだっけ?」と聞いた人がいて、「まあ既に二つ前のリリースから出ていて、使っている人もいるだろうし、正式に機能にしちゃえ」ということで今回のWhat's newに載っているみたいです。

  • セキュリティの改善

    • -P コマンドラインオプションとPYTHONSAFEPATH環境変数が追加され、それらを指定すると、危険性の高いディレクトリ(現在のディレクトリなど)を sys.path に追加しないようになります。
  • Format指定の文字として zが追加されました。

    • これまで、負の数が丸められてゼロになる場合には -0.0のように表記されていました。

      >>> print(f"{-0.001: .1f}")
      -0.0
      

      これを、zと使うと 0.0と表示されるようになります。

      >>> print(f"{-0.001: z.1f}")
      0.0
      

新規に追加されたモジュール

  • TOMLファイルフォーマットをパースする tomllib が追加されました。pyproject.tomlが標準になっているのに読み込みのライブラリがない状態だったので順当かと思います。なおTOMLの書き出しはサポートしていません。
  • WSGI特有の型の型チェックのためにwsgiref.typesが追加されました。

モジュールの改善

datetime

  • datetime.timezone.utcのエイリアスとして datetime.UTCが使えるようになりました。timezoneとしてUTCを指定する時に少し楽になる。
  • datetime.date.fromisoformat()datetime.time.fromisoformat()datetime.datetime.fromisoformat()がISO8601フォーマットのほとんどをサポートするようになりました。変な表現ですが、今まではパースできないものがあって、特にタイムゾーンZの表記(+00:00と同等)をサポートできていませんでした。これまではこの問題を避けるためにわざわざ前処理していたので、この変更はとても嬉しい!

fractions

  • PEP-515で定義されているアンダースコア入の文字列での初期化が出来るようになりました。例えば以下のような事ができるようになります。3.10までは ValueErrorになっていました。
from fractions import Fraction
print(Fraction("111_222.333"))
  • Fractionは__int__メソッドを実装しました。これまでも int(some_fraction)という形で正しい値が返ってきていましたがこれは __trunc__メソッドを実装していたからでした。isinstance(some_fraction, typing.SupportsInt)を正しく動作させるのにこの __int__メソッドが必要です。

math

  • exp2(x)(2のx乗)、cbrt(x)(xの三乗根)が追加されました。
  • pow()の挙動を IEEE754仕様と一貫性のある動きをするようになりました。

operator

  • operator.call(obj, *args, **kwargs)が追加されました。obj(*args, **kwargs)と同じ動作を関数呼び出しで実現できます。

pathlib

  • glob()rglob()の呼び出し時に与えるパターンがパスの区切り文字(Posixの場合 /、Windowsは \)だった場合にディレクトリのみを返すようになりました。

sqlite3

  • set_authorizer()Noneを与えることでオーソライザーを無効化できます
  • create_collation()に与える名前はUnicode文字を含んでも良いことになりました。不正な文字が使われた場合はsqlite3.ProgrammingErrorではなく、UnicodeEncodeErrorが返ります。
  • sqlite3モジュールの例外はsqlite_errorcodesqlite_errornameを属性として持ちます。それぞれ、SQLiteの拡張エラーコードとSQLiteのエラー名です。
  • sqlite3.Connectionsetlimit()getlimit()メソッドが追加され、接続毎にリミット設置できるようになりました。
  • SQLiteがコンパイルされたスレッドモデルに従って、 sqlite3.threadsafetyが動的に設定されるようになりました。(これまでは1にハードコードされていました)

threading

  • Unix環境で、sem_clockwait()がCライブラリ (glibc2.30以上)で利用可能になっている場合、threading.Lock.acquire()メソッドはタイムアウトのためにシステムクロックの代わりにモノトニッククロック(単調増加時計)を使うようになります。これによりシステムクロックの変更による影響をうけなくなります。

time

  • Unix環境で、time.sleep()clock_nanosleep()nanosleep()関数を使うようになります。これが利用可能な場合は、これまでのマイクロ秒の解像度だったものがナノ秒の解像度になります。

unicodedata

  • Unicodeのデータベースが 14.0.0 にアップデートされました。

types.EllipsisTypetypes.NoneTypetypes.NotImplementedTypeが(再)導入されました。

最適化

  • %s%r%aだけを含むC風文字列フォーマットの処理速度を改善し、f文字列式と同程度の速度になりました。
  • 「ゼロコスト」例外が実装されました。例外が起こらない場合の try文の実行コストはほぼ排除されました。
  • キーワード引数に依るメソッド呼び出しのコストがバイトコードの変更により速くなりました。これまでは、位置引数の場合にのみ最適化が施されていました。
  • 純粋ASCII文字列の unicodedata.normalize()によって定数時間で正規化できるようになりました。
  • math.comb()math.perm()が大きな引数の場合に10倍以上速くなりました。

速度向上

Python 3.11はPython 3.10と比べて平均で 25%速くなりました。ワークロードにも寄りますが、10%から60%の速度向上が見込めます。これは「起動時の高速化」と「実行時の高速化」の二つによって達成されています。

起動時の高速化

Python 3.10まではコアのモジュールであったとしても他のPythonのプログラム同様に __pycache__ディレクトリに格納されたコンパイル済みのバイトコードを取り出し動的にヒープ領域に貼り付けてから利用していました。これを、3.11では予め静的に貼り付けておいて、すぐに評価開始できるようにしています。これによって起動時間が10-15%高速化されました。

実行時の高速化

以下の改善で高速化をしています。

  • 関数呼び出し時の「フレーム」作成をより簡易に ... 3〜7%の速度向上
  • 関数呼び出しのインライン展開 ... 1〜3%の速度向上
  • コードの中で型が安定している部分を見つけてきて、よりその型に特化した実装に入れ替える
    ... 最大 2~20%の速度向上

廃止予定

  • lib2to3パッケージと 2to3ツールは廃止予定となり、3.10以降のPythonコードをパースできなくなるかも知れません。

  • webbrowser.MacOSXは廃止予定となり、Python 3.13で削除されます。これはテストされておらず、ドキュメントも有りませんでした。

  • 次の unittestの関数は廃止候補となり、Python 3.13で削除されます。

    • unittest.findTestCases()
    • unittest.makeSuite()
    • unittest.getTestCaseNames()

    代わりに、TestLoaderのメソッドを使ってください。

    • unittest.TestLoader.loadTestsFromModule()
    • unittest.TestLoader.loadTestsFromTestCase()
    • unittest.TestLoader.getTestCaseNames()
  • PEP-594では標準ライブラリからのモジュールの削除が提案されています。それらには

    • 既に使われなくなったフォーマット(SUNやコモドールのためのフォーマット)
    • 古いOSのサポート(Mac OS9)
    • セキュリティ観点でより良い代替手段が提供されている

    などが含まれていてて、3.11のタイミングでは以下のモジュールが廃止予定となります。

    • aifcaudioopcgicgitbchunkcryptimghdrmailcapmsilibnisnntplibossaudiodevpipessndhdrspwdsunautelnetlibuuxdrlib

機能削除

  • Python3.9で廃止予定になっていたbinhexモジュールが削除されました。加えて、以下の binasciiモジュールの関数も削除されました。
    • a2b_hqx(), b2a_hqx()
    • rlecode_hqx(), rledecode_hqx()
  • Python3.9で廃止予定になっていたdistutilsbdist_msiコマンドが削除されました。代わりに bdist_wheelを使ってください。
  • 深刻なセキュリティ上の懸念からPython3.9で無効化されていたasyncio.loop.create_datagram_endpoint()reuse_addressパラメータが完全に削除されました。
  • Python3.9で廃止予定になっていたxml.dom.pulldom.DOMEventStreamwsgiref.util.FileWrapperfileinput.FileInput__getitem__()メソッドが削除されました。
  • 廃止予定だった unittest の多くの古い機能が削除されました。
  • 廃止予定だった gettext の関数が削除されました(lgettext(), ldgettext(), lngettext() ldngettext()
  • configparserモジュールから以下が削除されました
    • SafeConfigParserクラス
    • ParsingErrorクラスのfilename属性
    • ConfigParserクラスの readfp()メソッド
  • レガシーのジェネレータベースのコルーチンを async/await互換にする @asyncio.coroutineデコレータが削除されました。Python3.8で廃止候補になり元々は3.10で削除予定でしたが、今回の3.11での削除になります。代わりに async defを使ってください。

まとめ

Python 3.11の変更点についてまとめてみました。既にb5まで来ていて、順調にいくと来週にはRelease Candidateになります。これ以降は10月のリリースまで機能変更はないと思うので、とりあえず3.11に関しては今回で打ち止めです。時間があれば速度向上のあたりを深掘りしてみたいですが、まずは無事に3.11が出るのを見守りたいと思います。

148
91
3

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
148
91