Python 3.12の新機能紹介
Ryuji Tsutsui
関西オープンフォーラム2023セミナー資料
This work is licensed under a Creative Commons Attribution 4.0 International License .
はじめに
自己紹介
-
Ryuji Tsutsui @ryu22e
-
株式会社hokan 所属
-
Python歴は
12年くらい (主に Django) -
Python Boot Camp、
Shonan.py、 GCPUG Shonanなど コミュニティ活動も しています -
著書
(共著) :『 Python実践レシピ 』
PR
今日話したいこと
-
Python 3.12の
新機能を 紹介します
セミナーの構成
-
新しい
構文 -
パフォーマンスの
改善 -
デバッグ・モニタリング方法の
改善 -
その他新機能
このセミナーから得られるもの
-
Pythonが
どんな 進化を 遂げているかが 分かる -
実際に
Python 3.12を 使う 際に 役立つ 情報が 得られる
新しい構文
-
PEP 695 型ヒントの
新構文 -
PEP 701 f-stringが
ネスト可能に
PEP 695 型ヒントの新構文
-
ジェネリッククラス、
ジェネリック関数に 関する 構文 -
型エイリアスに
関する 構文
型ヒントとは
-
Python 3.5で
追加された 機能 -
コードに
型ヒントを 付ける ことで 型チェッカー (Mypy、 Pyrightなど)で 型ヒントと 矛盾する コードを 書いていないか 検証できる -
実行時に
型チェックは してくれない
型ヒントの例
def example(message: str, count: int) -> str:
return message * count
print(example("hello", 3)) # OK
print(example("hello", "3")) # NG
型ヒントの例(続き)
ジェネリッククラス、ジェネリック関数とは
特定の
具体的な
ジェネリッククラスの例
from typing import Generic, TypeVar
T = TypeVar('T') # 抽象的な型名を定義
# このクラスがジェネリッククラスであることを宣言する
class Example(Generic[T]):
def __init__(self, value: T) -> None:
self.value = value
def get_value(self) -> T:
return self.value
def get_type(self) -> type:
return type(self.value)
# クラス名の右に角括弧で具体的な型名を囲む
example1 = Example[int](1)
# 1 が出力される
print(example1.get_value(), example1.get_type())
example2 = Example[str]('hello')
# hello が出力される
print(example2.get_value(), example2.get_type())
ジェネリック関数の例
from typing import Sequence, TypeVar
T = TypeVar('T') # 抽象的な型名を定義
def first(l: Sequence[T]) -> T:
return l[0]
print(first([1, 2, 3])) # 1 が出力される
print(first("python")) # p が出力される
PEP 695登場以前のジェネリッククラス、ジェネリック関数の面倒な点
-
毎回
T = TypeVar('T')
を書くのが 面倒 -
ジェネリッククラスの
場合、 Generic
を継承する 必要が あるのが 面倒
PEP 695でジェネリッククラス、ジェネリック関数はどう変わったか
T = TypeVar('T')
やGeneric
を
Python 3.12でのジェネリッククラスの例
class Example[T]: # 角括弧でTを囲む(新構文)
def __init__(self, value: T) -> None:
self.value = value
def get_value(self) -> T:
return self.value
def get_type(self) -> type:
return type(self.value)
example1 = Example[int](1)
print(example1.get_value(), example1.get_type())
example2 = Example[str]('hello')
print(example2.get_value(), example2.get_type())
Python 3.12でのジェネリック関数の例
from typing import Sequence
def first[T](l: Sequence[T]) -> T: # 関数名の右に角括弧でTを囲む(新構文)
return l[0]
print(first([1, 2, 3]))
print(first("python"))
PEP 695で型エイリアスはどう変わったか
type文が
>>> # Python 3.11
>>> from typing import TypeAlias
>>> Point: TypeAlias = tuple[float, float]
>>> # Python 3.12
>>> type Point = tuple[float, float]
>>> type Point[T] = tuple[T, T] # ジェネリックの構文も使える
type文と既存の型エイリアスの違い
type文は
>>> type Foo = int | Bar # Barはこの時点では定義されていないがエラーにならない
>>> type Bar = str
>>> # Python 3.11までの書き方だとエラーになる
>>> from typing import TypeAlias
>>> Foo: TypeAlias = int | Bar
Traceback (most recent call last):
File "", line 1, in
NameError: name 'Bar' is not defined
PEP 701 f-stringがネスト可能に
f-stringとは
以下
フォーマット済み文字リテラル (短くして
f-string とも 呼びます) では、 文字列の 頭に f か F を 付け、 式を {expression} と 書く ことで、 Python の 式の 値を 文字列の 中に 入れ込めます。
https://docs.python.org/ja/3/tutorial/inputoutput.html#formatted-string-literals
f-stringの例
>>> name = "Python"
>>> f"Hello, {name}!" # 変数を埋め込める
'Hello, Python!'
>>> from datetime import datetime
>>> f"Today is {datetime.now():%Y-%m-%d}" # 式を埋め込める
'Today is 2023-11-11'
公式ドキュメントに「式を埋め込めます」とは書いているものの…
(Python 3.11までは)
>>> d = {"foo": 1, "bar": 2}
>>> # "{d[" までを文字列を認識してしまう(一応 f"{d['foo']}" で回避できる)
>>> f"{d["foo"]}"
File "", line 1
f"{d["foo"]}"
^^^
SyntaxError: f-string: unmatched '['
>>> # バックスラッシュも使えない
>>> f"{'\n'.join(['foo', 'bar'])}"
File "", line 1
f"{'\n'.join(['foo', 'bar'])}"
^
SyntaxError: f-string expression part cannot include a backslash
f-stringの仕様はどうなっている?
f-string導入に
The exact code used to implement f-strings is not specified.
PEP 701でどう変わったか
パーサーが
>>> d = {"foo": 1, "bar": 2}
>>> f"{d["foo"]}"
'1'
>>> f"{'\n'.join(['foo', 'bar'])}"
'foo\nbar'
>>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
'2'
>>> def example(s):
... return f"result: {s}"
...
>>> import random
f"{example(f"{random.randint(1, 10)}")}"
'result: 4'
PEP 701でどう変わったか(続き)
f-stringの
VS Codeの
パフォーマンスの改善
-
PEP 684 インタプリタごとに
固有の GILが 使われるように 変更 -
PEP 709 内包表記の
パフォーマンス改善
PEP 684 インタプリタごとに固有のGILが使われるように変更
GIL(global interpreter lock)とは
以下
CPython インタプリタが
利用している、 一度に Pythonの バイトコードを 実行する スレッドは 一つだけである ことを 保証する 仕組みです。
https://docs.python.org/ja/3/glossary.html#term-global-interpreter-lock
GILの例
以下のprint_hello()
関数が
import threading
def print_hello(): # この関数はGILにより同時に実行されない
print("Hello!")
threads = []
for _ in range(3):
thread = threading.Thread(target=print_hello)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
PEP 684でどう変わったか
-
インタプリタが
固有の GILを 持つサブインタプリタを 作成できるようになった -
つまり、
異なる サブインタプリタ間では GILが 起こらない
これで問題は解決した、と言いたいところだが…
-
今回追加されたのは
C言語から 利用できる Py_NewInterpreterFromConfig()
関数。Pythonコードからは 利用できない -
PEP 554 で
追加される interpreters
モジュールを使う ことで、 初めて PEP 684の 恩恵を 受けられる (Python 3.13で 実装予定)
Python 3.13のリリース予定日は?
PEP 719に
Python 3.13.0a1
で interpreters
モジュールを試してみると
interpreters
モジュールを未実装だったので
>>> import interpreters
Traceback (most recent call last):
File "", line 1, in
ModuleNotFoundError: No module named 'interpreters'
PEP 709 内包表記のパフォーマンス改善
内包表記とは
内包表記とは、
def example(numbers):
"""numbers: 整数のリスト"""
# リストの各要素に"No."を付けたリストを作成する
return [f"No.{i}" for i in numbers]
同じことをfor文でやるとこうなる
def example(numbers):
results = []
for i in numbers:
results.append(f"No.{i}")
return results
PEP 709登場前の内包表記の問題
内包表記は
PEP 709でどう変わったか
関数オブジェクトを
違いを確認してみる
Pythonのdis
と
dis
モジュールの使い方
$ python -m dis {Pythonコードのファイル名}
対象のPythonコード
以下のpep709_example.py
を
def example(numbers):
return [f"No.{i}" for i in numbers]
Python 3.11の場合
Python 3.12の場合
デバッグ・モニタリング方法の改善
-
PEP 669
sys.monitoring
の追加 -
Linux perfの
CPythonサポート (PEP番号はなし) -
エラーメッセージの
改善 (PEP番号はなし)
PEP 669 sys.monitoring
の追加
PEP 669登場前
-
従来の
プロファイラー ( profile
、cProfile
など)はパフォーマンスに 重大な 問題を 引き起こす ことが あった -
稼働中の
アプリケーションに いつ、 どの 関数、 メソッドが 呼び出されているか 調べるには 高速な プロファイラーが 必要
PEP 669 sys.monitoring
の追加
-
sys.monitoring
はCPython上で 動作する 高速な プロファイラー -
関数や
メソッド呼び出しなどの タイミングで 呼び出すフック関数を 登録できる
sys.monitoring
の主な使い方(1)
以下で
-
sys.monitoring.use_tool_id()
: ツールIDを登録 -
sys.monitoring.register_callback()
: フック関数を登録 -
sys.monitoring.set_events()
: 監視するイベントを 登録
sys.monitoring
の主な使い方(2)
以下で
-
組み込み関数の
compile()
、exec()
関数を使って コードを 実行
sys.monitoring
の主な使い方(3)
以下で
-
sys.monitoring.set_events()
: 監視するイベントの 登録解除 -
sys.monitoring.free_tool_id()
: ツールIDを解放
sys.monitoring
のサンプルコード
前述の
https://gist.github.com/ryu22e/87411710176fd1d0ba0f95b0e5f9d6e0
Linux perfのCPythonサポート(PEP番号はなし)
Linux perfとは
-
Linuxカーネル2.6.31以降で
利用可能な パフォーマンス分析ツール -
プログラムの
どこで どれだけCPUを 使っているか計測できる
Linux perfの使用例
python my_script.py
をperf.data
を
$ perf record -F 9999 -g -o perf.data python my_script.py
Linux perfの使用例
出力されたperf.data
をperf report
コマンドで
$ perf report --stdio -n -g
実際に計測してみる
計測対象の
def foo(n):
result = 0
for _ in range(n):
result += 1
return result
def bar(n):
foo(n)
def baz(n):
bar(n)
if __name__ == "__main__":
baz(1000000)
Python 3.11でLinux perfを使った場合
以下を
https://gist.github.com/ryu22e/0f5f52712194e4e38c211958288e6267#file-python3-11-md
Python 3.12でLinux perfを使った場合
以下を
https://gist.github.com/ryu22e/0f5f52712194e4e38c211958288e6267#file-python3-12-md
Python 3.12でperfプロファイリングを有効にするには
-
環境変数
PYTHONPERFSUPPORT=1
を設定して 実行 -
-X perf
オプションを付けて 実行 -
sys
モジュールが提供する APIを 使った コードを 入れる (次の スライド参照)
sysモジュールを使ってperfプロファイリングを有効にする例
import sys
sys.activate_stack_trampoline("perf") # 計測開始
do_profiled_stuff() # 計測中
sys.deactivate_stack_trampoline() # 計測終了
non_profiled_stuff()
activate_stack_trampoline
エラーメッセージの改善(PEP番号はなし)
Pythonエラーメッセージは改善を続けている
Python 3.10
CPythonの
https://docs.python.org/ja/3/whatsnew/3.10.html#better-error-messages
Python 3.10でのエラーメッセージの例
Python 3.9
>>> if a
File "", line 1
if a
^
SyntaxError: invalid syntax
Python 3.10
>>> # :が足りないと指摘してくれる
>>> if a
File "", line 1
if a
^
SyntaxError: expected ':'
「Better error messages」についてもっと詳しく知りたい人は
Gihyoさんの
Python 3.12でのエラーメッセージの例(1)
NameError
時に
Python 3.11
>>> sys
Traceback (most recent call last):
File "", line 1, in
NameError: name 'sys' is not defined
Python 3.12
>>> sys
Traceback (most recent call last):
File "", line 1, in
NameError: name 'sys' is not defined. Did you forget to import 'sys'?
Python 3.12でのエラーメッセージの例(2-1)
NameError
時にself.属性名
を
Python 3.11
>>> class Example:
... def __init__(self):
... self.foo = 1
... def hello(self):
... a = foo
...
>>> Example().hello()
Traceback (most recent call last):
File "", line 1, in
File "", line 5, in hello
NameError: name 'foo' is not defined
Python 3.12でのエラーメッセージの例(2-2)
Python 3.12
>>> class Example:
... def __init__(self):
... self.foo = 1
... def hello(self):
... a = foo
...
>>> Example().hello()
Traceback (most recent call last):
File "", line 1, in
File "", line 5, in hello
NameError: name 'foo' is not defined. Did you mean: 'self.foo'?
Python 3.12でのエラーメッセージの例(3-1)
import文の
Python 3.11
>>> # importとfromの順番が逆
>>> import os from environ
File "", line 1
import os from environ
^^^^
SyntaxError: invalid syntax
Python 3.12でのエラーメッセージの例(3-2)
Python 3.12
>>> import os from environ
File "", line 1
import os from environ
^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Did you mean to use 'from ... import ...' instead?
Python 3.12でのエラーメッセージの例(4-1)
import文の
Python 3.11
>>> # chainmapはChainMapのtypo
>>> from collections import chainmap
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'chainmap' from 'collections' (/****/__init__.py)
Python 3.12でのエラーメッセージの例(4-2)
Python 3.12
>>> from collections import chainmap
Traceback (most recent call last):
File "", line 1, in
ImportError: cannot import name 'chainmap' from 'collections' (/****/__init__.py).
Did you mean: 'ChainMap'?
約1分休憩
給水します🚰
大阪でお勧めの美味しい店募集
明日
その他新機能
-
PEP 692
**kwargs
引数に付けられる 型ヒントに 関する 改善 -
PEP 698 メソッドを
オーバーライドする 際の typoを 防ぐ override
デコレーターの登場 -
PEP 688 Pythonコードから
バッファプロトコルに アクセスできるように
※ PEP 688は
PEP 692 **kwargs
引数に付けられる型ヒントに関する改善
Pythonの関数の引数指定方法
Pythonの
>>> def example(a, b):
... ...
...
>>> example(1, 2) # 関数定義に書かれた順番に値を指定(位置引数)
>>> example(a=1, b=2) # 引数名と値をセットで指定(キーワード引数)
**kwargs
引数とは
-
引数名の
先頭に **
を付けると、 どんな キーワード引数でも 受け付ける 引数になる -
関数内では
kwargs
を辞書型の 値と して 扱う -
読み方は
「クワーグス」 (参考URL: https://youtu.be/WcTXxX3vYgY?t=9) -
keyword argumentsの
略 -
文法的には
先頭に **
があれば どんな 名前でも いいが、 慣習的に kwargs
と書く
**kwargs
引数の例
>>> def example(**kwargs):
... print(kwargs)
...
>>> example(foo=1, bar=2)
{'foo': 1, 'bar': 2}
>>> example(last_name="Tsutsui", first_name="Ryuji")
{'last_name': 'Tsutsui', 'first_name': 'Ryuji'}
Python 3.11までの **kwargs
引数への型ヒントの付け方
**kwargs
引数へのすべての
def example(**kwargs: str) -> None:
...
example(foo="test1", bar="test2") # すべてのキーワード引数が文字列なのでOK
example(foo="test1", bar=2) # bar引数が整数値なのでNG
PEP 692でどう変わったか
typing.TypedDict
とtyping.Unpack
を**kwargs
引数に
from typing import TypedDict, Unpack, assert_type
class Book(TypedDict):
title: str
price: int
def add_book(**kwargs: Unpack[Book]) -> None:
assert_type(kwargs, Book) # エラーにならない
add_book(title="Python実践レシピ", price=2790) # OK
add_book(
title="Python実践レシピ",
price="2,970円(本体2,700円+税10%)", # NG
)
**kwargs
引数に TypedDict
を指定することによるメリット(1)
TypedDict
をtypoを
from typing import TypedDict, Unpack, assert_type
class Book(TypedDict):
title: str
price: int
def add_book(**kwargs: Unpack[Book]) -> None:
assert_type(kwargs, Book) # エラーにならない
# pricaはBookクラスに存在しないのでエラーになる
add_book(title="Python実践レシピ", prica=2790)
**kwargs
引数に TypedDict
を指定することによるメリット(2-1)
TypedDict
を「引数の
class Auth:
"""認証情報"""
...
def request(url: str, auth: Auth | None = None) -> None:
"""url引数で指定したURLにリクエストを送る。
認証が必要な場合はauth引数に認証情報を指定"""
...
# auth引数を省略している
request("https://example.com")
# auth引数に明示的にNoneを指定している
request("https://example.com", auth=None)
**kwargs
引数に TypedDict
を指定することによるメリット(2-2)
TypedDict
をこの
**kwargs
引数に TypedDict
を指定することによるメリット(2-3)
TypedDict
を明示的に
# Authのコードは省略
class Empty(Auth):
...
EMPTY = Empty()
def request(url: str, auth: Auth | None | Empty = None) -> None:
...
# auth=Noneをauth=EMPTYに書き換える
request("https://example.com", auth=EMPTY)
**kwargs
引数に TypedDict
を指定することによるメリット(2-4)
TypedDict
を**kwargs
引数を
from typing import TypedDict, Unpack, NotRequired
class OtherParams(TypedDict):
auth: NotRequired[Auth] # 入力必須ではない場合はNotRequiredを指定
# authの代わりに**kwargsを指定する
def request(url: str, **kwargs: Unpach[OtherParams]) -> None:
if "auth" not in kwargs:
print("auth引数が省略された場合の処理が呼ばれた")
elif "auth" in kwargs and kwargs["auth"] is None:
print("auth引数に明示的にNoneを渡した場合の処理が呼ばれた")
PEP 698 メソッドをオーバーライドする際のtypoを防ぐ override
デコレーターの登場
override
デコレーターのPythonでメソッドをオーバーライドするには
メソッド名、
>>> class Base:
... def say_hello(self, name):
... print("Hello, " + name)
...
>>> class Example(Base):
... def say_hello(self, name):
... print("こんにちは、" + name)
...
>>> example = Example()
>>> example.say_hello("Taro")
こんにちは、Taro
typoがあるとオーバーライドできない
>>> class Example(Base):
... def say_hallo(self, name): # halloはtypo
... print("こんにちは、" + name)
...
>>> example = Example()
>>> example.say_hello("Taro") # 基底クラスBaseのsay_helloメソッドが呼ばれる
Hello, Taro
override
デコレーターを使うとどうなるか
typing.override
デコレーターを
from typing import Self, override
class Base:
def say_hello(self: Self, name: str) -> None:
print("Hello, " + name)
class Example(Base):
@override
def say_hallo(self: Self, name: str) -> None: # halloはtypo
print("こんにちは、" + name)
typoしているコードを型チェックすると
該当箇所が
PEP 688 Pythonコードからバッファプロトコルにアクセスできるように
(残り4分
バッファプロトコルとは何か、の前にプロトコルとは何か
特定の
プロトコルの例(1)
len()
関数は
>>> len([1, 2, 3]) # リストなら要素数
3
>>> len((1, 2, 3)) # タプルなら要素数
3
>>> len('Python') # 文字列なら文字数
6
プロトコルの例(2)
len()
関数には
>>> len(1)
Traceback (most recent call last):
File "", line 1, in
TypeError: object of type 'int' has no len()
>>> len(None)
Traceback (most recent call last):
File "", line 1, in
TypeError: object of type 'NoneType' has no len()
プロトコルの例(2)
>>> class Example:
... ...
...
>>> len(Example())
Traceback (most recent call last):
File "", line 1, in
TypeError: object of type 'Example' has no len()
プロトコルの例(3)
どんな
len()
関数の__len__()
メソッドを
>>> class Example:
... def __len__(self):
... return 123
...
>>> len(Example())
123
プロトコルについてもっと詳しく知りたい人は
Takayuki Shimizukawaさんの
それではバッファプロトコルとは何か
Pythonより
組み込みオブジェクトだとbytes
、bytearray
などが
PEP 688登場以前にあったバッファプロトコルの問題点
バッファプロトコルを
PEP 688登場以前にあったバッファプロトコルの問題点
関数、
PEP 688登場以前にあったバッファプロトコルの問題点
typing.ByteString
は
PEP 688でどう変わったか
__buffer__()
メソッドを
PEP 688でどう変わったか
バッファプロトコルをcollections.abc.Buffer
型が
まとめ
-
型ヒントの
新構文で ジェネリックや 型エイリアスが 楽に 書ける。 f-stringは ネスト可能に -
GILの
改善や 内包表記の インライン化などに より Pythonの パフォーマンスが 向上 -
新たな
デバックや モニタリング方法が 登場。 エラーメッセージも より 親切に -
型ヒントの
細か い 部分の 改善に より、 より 型安全な コードが 書けるように