型
Python でも TypeScript のように型を定義というか注釈を行うことができる。
メリットとしては
- 可読性が上がる(関数において引数に何を入れるのか、戻り値は何を期待するのかがわかる)
- 評価を行う際に型安全を担保できる
- 実行前に型エラーを検出することでデバッグの手間を減らす
といったことが挙げられる。
ただし、python は動的型付け言語(実行時に型チェックを行う)なのでmypy
を入れないと厳密に検査はできない。
簡単な例。
def add(x: int, y: int) -> int:
return x + y
# mypyを入れないと以下も実行できてしまう
return 'terrible'
普通はやらないはずだがこういうやばいシチュエーションを未然に防げたりもする。
class User:
# 初期化とクラス変数定義
def __init__(self, name: str)-> None:
self.name: str = name
# クラス変数の型を変えようとするやばい関数
def change_name_type(self, id: int)-> None:
self.name: int = id
if __name__ == '__main__':
u: User = User("Takeshi")
# やばい関数を実行しようとしてもIncompatible types in assignment (expression has type "int", variable has type "str")エラー
u.change_name_type(1)
print(type(u.name))
つまるところ
型はプログラムが構文的に正しくても式の意味が正しいかをチェックするための重要な要素
というところが肝なのである。
特に構文的に正しいことと意味として正しいかが必ずしもイコールではないという認識は私のようなひよっこには慣れないところ。
ちなみに構文的に正しいのをwell defined
といい、その狭義的な意味として意味として正しい = well typed
なんていい方をするらしい。
意味として正しければ逆説的に構文的にも正しくなり得る……という理論ではあるがwell defined
とwell typed
とでは後者のほうが権限が強く、それは正しく定義されているけれどwell typed
ではないと見なされ弾かれてしまう問題がある。
こういうところを期待して型評価をやっているのだからいいのでは? という疑問が当然出るが、それはwell typed
が完全であることが前提であるという話。
そもそも、well typed
が万能であるなら Python のような動的型付けの言語は存在する理由の大きなところを喪失してしまう。
とはいえ、不正な動作をしない・可読性を上げるといった保証の担保のために型注釈を適切に行なって型安全を求めていこうという意識は重要であるということなのだろう。
最後に
業務に入ってからレビューで
- なぜ
split()
ではなく、ast.literal_eval()
を使うのか? - 速度的には
for()
のネストが早いのにitertools.product()
を使うのか?
ということについて度々触れることがあり、その度に型を限定して安全に評価を行うことが大切ということを説かれました。
今回はその意味を自分の中で腹落ちさせるための回でした。
参考
番外
環境別に pip で管理しているライブラリのバージョンを分ける場合は venv で各環境作って pip install をしてそれぞれ base(共通ライブラリ)、production、local といったように分けて各環境の requirements に-r base.txt
を仕込む。
あとは
pip install -r production.txt # base.txtとproduction.txtのライブラリをインポート
とやれば環境ごとにライブラリのバージョンを別に管理できるようになる。
poetry とか使うとこのあたりは 1 ファイルで済むらしいが、venv が現状推奨されている環境ではありそうなのでこういったテクを覚えておきたいと思いました。