Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2021年の型駆動開発 / type-driven-design-in-2021-python

2021年の型駆動開発 / type-driven-design-in-2021-python

2021/05/07 CMScom techtalk

Peacock

May 07, 2021
Tweet

More Decks by Peacock

Other Decks in Programming

Transcript

  1. 2021 年の型駆動設計( 開発) CMScom Techtalk 2021/05/07

  2. 1. Type hint ざっくり復習 i. 関数定義のところから書くのがおすすめ 2. Python3.10 style な

    type hinting i. 3.9, 3.10 の Type hint 系新機能を紹介 ii. Pattern Matching, str.removeprefix() とか他の機能については話さない 3. 型を意識して設計するということ Table of Contents 2 / 21
  3. 書いている → 少し⾶ばして後半に議論したい 書いていない → 前に話したことを少し説明します はじめに: Type hint 書いてますか?

    3 / 21
  4. 2020/09/11 TechTalk で話したこと

  5. 5 / 21

  6. 6 / 21

  7. 7 / 21

  8. 3.10 Style な type hinting

  9. 次ページから1 つずつ⾒ていきます 3.8 → 3.9 ⼩⽂字始まりの標準コレクション型ヒント 3.9 → 3.10 Union

    型演算⼦ | 引数仕様変数 ( 原題: Parameter Specification Variables) 明⽰的な型エイリアス 3.8 から何が変わったか概要 9 / 21
  10. 3.9 からの新機能 list, dict, Iterable, etc... PEP 585 のページで列挙されている list[str]

    のように書く 前に話した from typing import List は書かなくていい リリースから5 年後( つまり2025 年) には⾮推奨になる collections 系の⾊々はライブラリ開発とかでもなければ普段使わなさそう re.Match, re.Pattern につくのは便利そう PEP 585: ⼩⽂字始まりの標準コレクション型ヒント 10 / 21
  11. 3.10 の新機能 Union の糖⾐構⽂として | が使えるようになった str | int ==

    Union[str, int] TypeScript とか Haskell がこの記法を使っている より直感的で記述量が減るのでうれしい def square(number: int | float) -> int | float: return number ** 2 # isinstance() でも使える >>> isinstance(1, int | str) True PEP 604: Union 型演算⼦ | 11 / 21
  12. 3.10 の新機能 Callable[T, R] の T に今までは tuple, Generics を渡せなかった

    str, int などの型 or elipsis ( ... ) なら⼤丈夫だった 以下の例のように書けるようになる デコレーターを書くときに便利そう( まだ使ったことない) P = ParamSpec("P") def validation: (f: Callable[P, str]) -> Callable[P, bool]: def inner(s: str) -> bool: retrun s.isascii() PEP 612: 引数仕様変数 ( 原題: Parameter Specification Variables) 12 / 21
  13. 3.10 の新機能 エイリアス型がエイリアスであるとより明⽰的になる # Before 3.10 JsonLikeDict = dict[Union[str, int],

    Any] # After 3.10 JsonLikeDict: TypeAlias = dict[Union[str, int], Any] # Union を新しい記法にしたバージョン JsonLikeDict: TypeAlias = dict[str | int, Any] PEP 613: 明⽰的な型エイリアス 13 / 21
  14. これらを Python3.7, 3.8 のコードで使うには from __future__ import annotations を書く __future__

    : 今後実装予定のモジュール 3.6 には⾮対応: PEP 563 dunder module (?) なので⼀番最初に書く from __future__ import annotations from datetime import datetime from __future__ import annotations を書く 14 / 21
  15. 型を意識して設計する 「型駆動開発」を1 年くらい実践して得た Best practices

  16. Optional は便利だけどコードが肥⼤化していく原因 def get_content() -> str | None: r =

    request.get("https://example.com") if r.status_code != 200: # ここがガード(早期リターン) logging.warning("HTTP response is %d!", r.status_code) return None return r.text ↑の関数を使うときにまたガードを書いて None を返すかもしれない 結果、先のメソッドまでガードを書く必要があり可読性が落ちる Optional をなるべく使わない 16 / 21
  17. この場合なら raise RuntimeError なりしてしまったほうがすっきりする Python は例外を発⽣させるコストが( ⽐較的) 低いのでパフォーマンスも ⼤丈夫なはず Python

    に null 安全なメソッドがないのも⼀因だけど、あるとそれはそれで 乱⽤してしまう Null 安全とは: Null(None) を渡しても例外が発⽣しないメソッド Plone の View とかがこれで書けるかはわからないけど、内部に使っている 関数レベルでならできそう 17 / 21
  18. Language Server(LS): エディタで補完機能などをサポートしてくれる機能 Pylance(VS Code extension), Jedi が有名 裏でプロセスが⾛っていて、エディタと通信して動く ⾃明な型は書かない、Language

    Server の推論がやってくれる 変数に型ヒントつける基準は推論が効かなくなる( Any 判定される) とき 必要があればガードをする。そうすれば取りうる型の範囲が狭まる 次ページにサンプルがあります Language Server の⼒を借りる 18 / 21
  19. i: int = random.randint(1, 5) # `: int` は書かない r

    = request.get("http://example.com/api.json") d: dict = json.loads(r.text) if r.status_code == 200 else {} if d == {}: # エラー処理 logger.error("Response is empty!") raise RuntimeError pass # ここで `d` は空dictの可能性がなくなっている 19 / 21
  20. 関数定義のところから Type hint 書いていきましょう Python3.10 style な type hinting ⼩⽂字始まりの標準コレクション型ヒント

    (3.9) Union 型演算⼦ | (3.10) 引数仕様変数 (3.10) 明⽰的な型エイリアス (3.10) 型を意識して設計すると全体の⾒通しが良くなるのでおすすめ まとめ 20 / 21
  21. Thank you for listening! This slide is made by marp