はじめに
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を元に書いています。
- 以下を追加
- 「その他の言語の変更」を追加
- 「新規に追加されたモジュール」を追加
- 「モジュールの改善」に
datetime
とpathlib
を追加 - 「廃止予定」に標準ライブラリからのモジュールの削除(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.Required
とtyping.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_errorcode
とsqlite_errorname
を属性として持ちます。それぞれ、SQLiteの拡張エラーコードとSQLiteのエラー名です。 -
sqlite3.Connection
にsetlimit()
と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.EllipsisType
、types.NoneType
、types.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のタイミングでは以下のモジュールが廃止予定となります。
-
aifc
、audioop
、cgi
、cgitb
、chunk
、crypt
、imghdr
、mailcap
、msilib
、nis
、nntplib
、ossaudiodev
、pipes
、sndhdr
、spwd
、sunau
、telnetlib
、uu
、xdrlib
機能削除
- Python3.9で廃止予定になっていた
binhex
モジュールが削除されました。加えて、以下のbinascii
モジュールの関数も削除されました。-
a2b_hqx()
,b2a_hqx()
-
rlecode_hqx()
,rledecode_hqx()
-
- Python3.9で廃止予定になっていた
distutils
のbdist_msi
コマンドが削除されました。代わりにbdist_wheel
を使ってください。 - 深刻なセキュリティ上の懸念からPython3.9で無効化されていた
asyncio.loop.create_datagram_endpoint()
のreuse_addressパラメータが完全に削除されました。 - Python3.9で廃止予定になっていた
xml.dom.pulldom.DOMEventStream
、wsgiref.util.FileWrapper
、fileinput.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が出るのを見守りたいと思います。