Pythonではじめる今風な型プログラミング

6/25(Sat), OSC2021 Online/Hokkaido

今日話すこと

  1. そもそも: 型ヒントとは
  2. 型ヒント入門編: 小さく始める
    1. 関数の戻り値・引数
    2. 標準で使える組み込み型
  3. 少し発展編: typingモジュール
    1. Genarator, Callable, TypeVar, Genarics, etc...
  4. 最近のアップデート・新機能を以前のバージョンでも使うには

自己紹介

自己紹介(続き)

  • OSCは2019 Tokyo/Fallから
  • 2020はちょこちょこスタッフやってたりしてました
  • パブリックな場所で話すのが初めてなのでどうかお手柔らかに🙏
    • Zoom発表さみしいのでリアクションたくさんもらえると嬉しいです
    • Twitter実況も歓迎(あとで見に行きます
  • 資料はSpeakerDeckにアップロード済みです

Motivationとこの発表のゴール

Let's begin!

そもそも: 型ヒントとは

  • 3.5まで完全な動的型付け言語だったPythonに、型を導入しようという話
  • あくまで「ヒント」でありコメント
    • 実行時に評価はされず、TypeSciptのようにコンパイルエラーも出ない
def greet(name: str) -> str:
    return "Hello, " + name

型ヒント入門編: 小さく始める

最初から頑張って書くのはつらいので小さく始める

まずは関数の引数・戻り値から

  • 全部に書こうとしなくていい
    • JavaやCみたいに全部書く必要はない
  • 基本的に型をつけるときは名前の後ろに:を書いて型
  • 関数の戻り値は->を使う
def greet(name: str) -> str:
    return "Hello, " + name

何が嬉しいのか?

  • エディタで参照したときに型がわかる
  • 間違ったものを渡そうとしたときに怒ってくれる


何もimportしなくていい組み込み型

  • bool, bytes, float, int, str: 何もしなくても使える
  • None: 何も返さない関数に使う
  • dict, frozenset, list, set, tuple

(3.9から非推奨) typingモジュールからimportする

  • Generics系は3.9まではfrom typing import ...を書いていた
    • コレクション、プロトコル関係など
  • 3.9からは後述する書き方になって、非推奨に

Any

  • あらゆる型のインスタンスを保持できる
  • 使わないに越したことはない
    • 必要なときにはtypingからimportして使う
from typing import Any

unknown_variable: Any

少し発展編: Generics型

合併型(Union)

Union: 合併型。3.10以降は|で表せる
例:文字列と整数をどっちも受け入れる関数

from __future__ import annoations  # 3.7 - 3.9は必要
def normalize_year(year: int | str) -> int:
    if isinstance(year, int): return year  # ここではyearは数値
    # これ以降ではyearは文字列
    if year.startswith("昭和"): return int(year[2:]) + 1925
    elif year.startswith("平成"): return int(year[2:]) + 1988
    elif year.startswith("令和"): return int(year[2:]) + 2018
    else: raise ValueError('unsupported style')
from typing import Union  # for 3.6
def normalize_year(year: Union[int, str]) -> int: pass

Optional型

  • nullable, NoneとのUnionと等価
    • Unionと同じように振る舞ってくれる
  • 関数の戻り値とかに使うと伝播してしまうので、使い方は要注意
from typing import Optional
age: Optional[int]
age = 17
age = None  # これも有効
age: int | None  # 3.9以降ならこれでもいい

Callable(呼び出し可能オブジェクト)

デコレーター関数など、関数を引数に取る関数を書くときに使える

from collections.abc import Callable  # 3.9以降
from fuctools import wraps
from typing import Callable  # 3.8以前
def validate(func: Callable) -> Callable[..., Callable | tuple[Response, Literal[400]]]:
    @wraps(func)
    def wrapper(*args, **kw) -> Callable | tuple[Response, Literal[400]]:
        try:
            j = request.json
            if j is None: raise BadRequest
        except BadRequest:
            return jsonify({"data": [], "errors": {"message": ERROR_MESSAGE, "code": 400}}), 400
        return func(*args, **kw)
    return wrapper

最近のアップデート・新機能を以前のバージョンでも使うには

最近のアップデート事情

https://www.python.org/downloads/

バージョン ステータス 初回リリース EOS PEP
3.9 バグ修正 2020-10-05 2025-10 596
3.8 バグ修正 2019-10-14 2024-10 569
3.7 セキュリティ 2018-06-27 2023-06-27 537
3.6 セキュリティ 2016-12-23 2021-12-23 494

__future__ モジュール: (dunder future) とは

  • 後方互換性のために存在している
  • 破壊的変更がいつ導入されて、必須になったかが書かれている
  • typingの他にも3.xの機能を2.xでも呼び出すときなどに使っていた
    • ex) print_func, unicode_literals etc ...
  • refs: 公式ドキュメント__future__, future statement

3.9, 3.10で使えるようになった(る)typing関連の新機能たち

3.9から: 標準Collections型のGenerics

  • Generics型定義([]を使って中の型を書ける)のimport元が変わった
  • 3.8まではtypingからだったけど、分散した
  • list, tuple, dictなどの__builtins__なら何もせずに小文字始まりにする
  • collections系(deque, defaultdict)などはcollectionsから
  • iterable, callableなどプロトコル関係はcollections.abcから
  • 正規表現はre
  • コンテキスト関連はcontextlib

3.10から: Union型演算子

  • 前述した合併型(Union)が演算子として使える
  • isinstance()で聞くときにも使える
  • TypeSciptなどがこの記法なのでより直感的
  • 3.10の他のtyping関連の新機能は複雑なのでは紹介しない

まとめ

  • 小さいところから、こつこつと
    • 関数の返値、戻り値からやるのがおすすめ
  • ここ1,2年で注目が高まってきて、アップデートも速い分野
  • 書いていて気持ちいい、素敵なPythonライフ?を!