🚀

Python3.12の新機能ハイライト

2023/07/08に公開

Python3.12の注目新機能

What’s new in Python 3.12を全部読むのは面倒だなと感じる人向けにオススメ機能を独断と偏見でリストアップ。

Type Parameter Syntax(イチオシ!)

モダンな型付き言語と同じ文法でジェネリクスをかけるようになります。

これまで

クラスや関数でGenericsを定義するためにはTypeVar文法を使う必要がありました。クラス定義の際にはGenericを継承する必要がありました。さらにオプションでcovariantやboundを定義する文法はあまりクリーンではありませんでした。covarianceとかcontravarianceってなに?(英語)

from typing import Generic, TypeVar

_T_co = TypeVar("_T_co", covariant=True, bound=str)

class ClassA(Generic[_T_co]):
    def method1(self) -> _T_co:
        ...
	
_T = TypeVar("_T")

def func(a: _T, b: _T) -> _T:
    ...

これから

JavaやRustでおなじみの文法でクリーンにジェネリクスを表現できるようになりました。

class ClassA[T: str]:
    def method1(self) -> T:
        ...
	
def func[T](a: T, b: T) -> T:
    ...

GenericなTypeAliasを新文法typeで定義できます。

type ListOrSet[T] = list[T] | set[T]

float_list_or_set: ListOrSet[float]  # valid

TypedDictで**kwargsをアノテーションする

これまで

これまで**kwargsは単一の型でアノテーションすることしかできませんでした。

def foo(*args: str, **kwds: int): ...

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)

またGenericなTypeAliasを定義することはできませんでした。

from typing import TypeAlias

_T = TypeVar("_T")

ListOrSet: TypeAlias = list[_T] | set[_T]

float_list_or_set: ListOrSet[float]  # invalid 

これから

kwargsをTypedDictで正確にアノテーションできるようになりました。

from typing import TypedDict, Unpack

class Movie(TypedDict):
  name: str
  year: int

def foo(**kwargs: Unpack[Movie]): ...

foo(name="hoge", year=3)  # valid
foo(undefined_key=None)  # invalid

Override Decorator

これまで

これまでPyrightなどの言語サーバーにとってサブクラスのメソッドが親クラスのメソッドをOverrideしてることを認識する方法はありませんでした。このため親クラスのメソッドの名前が変更されると本来サブクラスのメソッド名もかわるべきですが、言語サーバーにこれを検知する方法はありませんでした。

これから

from typing import override

class Base:
  def get_color(self) -> str:
    return "blue"

class GoodChild(Base):
  @override  # ok: overrides Base.get_color
  def get_color(self) -> str:
    return "yellow"

class BadChild(Base):
  @override  # type checker error: does not override Base.get_color
  def get_colour(self) -> str:
    return "red"

@overrideデコレータを明示的にメソッドに付与することで、親クラスと整合性が取れないメソッド定義をエラーとして扱うようになります。

Nested f-string

これまで

f-stringの最大ネスト数(する必要ある?)は4でした。

f"""{f'''{f'{f"{1+1}"}'}'''}"""

これから

無限にネストできます。やったー。

f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"

Pathlibのちょい進化

これまで

".."を含む相対パスの計算はos.path.relpathではできましたが、pathlib.Path.relative_toではエラーになっていました。

parent = "./"
child = "./data"
print(os.path.relpath(child, parent))  # data
print(os.path.relpath(parent, child))  # ..

parent = Path("./")
child = Path("./data")
print(child.relative_to(parent))  # data
print(
    parent.relative_to(child)
)  # Value error '.' is not in the subpath of 'data' OR one path is relative and the other is absolute.

これから

os.path.relpathと一貫する形で親フォルダにもさかのぼることができます。

parent = "./"
child = "./data"
print(os.path.relpath(child, parent))  # data
print(os.path.relpath(parent, child))  # ..

parent = Path("./")
child = Path("./data")
print(child.relative_to(parent))  # data
print(parent.relative_to(child))  # ..

まとめ

タイプアノテーションかなり完成してきました。もうTypeScriptのように思った型を効率よく書く方法はほぼすべてそろってきたんじゃないでしょうか。OSSもpolarsのような新しいライブラリは当然のことPyTorchのような古いライブラリのサポートもかなり充実してきているとおもいます。

皆様も新規プロジェクトでは必ずタイプアノテーションをつけるようにしましょう。

Discussion