はじめに
みなさん。Pythonで型書いてますか?最近は型の重要性を再認識しているので、皆さんにもぜひPythonで型を書いて頂きたいと思ってこの記事を書きました。
注意事項として今回の記事では下記の事項については言及しません。
- 型チェックツールの導入方法(mypy,pyrightなど)
今回の内容は以前の書いた記事の補足内容となっていますので、以前の記事ももしよければ参照ください。
そもそもPythonでなぜ型を書くのか?
Pythonは動的型付き言語なので、型を書かなくてもプログラムは動きます。型を書かないことで、コードの量は少なくなりますし、初学者にとっても習得しやすい言語となっていることはメリットかと思います。
ただし、ある程度の行数のコードを書く場合、プログラムを複数人でメンテナンスする場合、型がないと以下の様な問題が発生する。
- 関数を見ただけでは、返り値がどのようなものか分からない
- 変数に想定外の値が代入されていても分からない
なので、プログラムを長期にわたって保守するという観点からみると、型がある方が優位性があると思います。
例えば、以下のような関数がある場合を考えます。
def add(a, b):
"""引数を足した結果を返す"""
return a + b
def add_type(a: int, b: int) -> int:
"""引数を足した結果を返す"""
return a + b
同じ内容の関数ですが、片方は型があり、もう片方は型がありません。
実際に以下のように使うことができますが、関数の作成者は文字列が入ることを想定していなかった場合、型があるadd_type関数の方ではエディタでエラーが表示されます。
print(add('1', '2'))
print(add_type('1', '2'))
関数の作成者と利用者が必ずしも同じとは限りませんし、自分で作成した関数でも時間がたてば内容を忘れます。型をつけることで自分が想定したとおりに関数を利用してもらうことが期待できます。
型入門
組み込み型(list,dict,tuple)
listなどについてはPython3.8までと3.9で書き方が異なります。従来のtypingを使う方法が非推奨になり、組み込み型を利用する方法が推奨になっています。
Python3.8の場合
from typing import Dict, List, Tuple
val_e: Dict[str, int] = {'size': 12, 'age': 24}
val_f: List[str] = ['taro', 'jiro']
val_g: Tuple[str, int] = ('name', 12)
Python3.9の場合
val_e: dict[str, int] = {'size': 12, 'age': 24}
val_f: list[str] = ['taro', 'jiro']
val_g: tuple[str, int] = ('name', 12)
Finalについて
Python3.8からFinalがデフォルトで使用できるようになってます。定数(再代入不可)を定義することで、再代入されないようにすることができます。
from typing import Final
TABLE_NAME: Final[str] = 'sample'
再代入しようとするとエラーが表示されます。
定数にはtupleを積極的に使う
listとtupleは同じような機能があるので、ついついlistを使いがちですが、tupleの方がメリットが多い場合があるので紹介します。
listの場合、内部の情報を書き換えることが可能ですが、定数として定義するものは、内部の情報を書き換えられることを望まないことがほとんどです。そのような場合はtupleで定義すると内部情報が変更される心配がありません。
Python3.8の場合
from typing import Final, Tuple, List
# tupleなので変更不可
NAME_TUPLE: Final[Tuple[str, str]] = ('taro', 'jiro')
# listは変更可能
NAME_LIST: Final[List[str]] = ['taro', 'jiro']
NAME_LIST[0] = 'saburo'
Python3.9の場合
from typing import Final
# tupleなので変更不可
NAME_TUPLE: Final[tuple[str, str]] = ('taro', 'jiro')
# listは変更可能
NAME_LIST: Final[list[str]] = ['taro', 'jiro']
NAME_LIST[0] = 'saburo'
定数にはnamedtupleを積極的に使う
定数にdictを使う場合もあるかと思います。その場合namedtupleを使うと内部情報が変更されないので安心です。
from typing import NamedTuple, TypedDict
class StudentInfoTuple(NamedTuple):
name: str
age: int
class StudentInfoDict(TypedDict):
name: str
age: int
# namedtupleの場合、内部情報を書き換えることができない
TARO: Final[StudentInfoTuple] = StudentInfoTuple('taro', 12)
# 下記行はエラーになる
# TARO.name = 'taro2'
# dictの場合内部情報を書き換えることができる
JIRO: Final[StudentInfoDict] = {'name': 'jiro', 'age': 9}
JIRO['name'] = 'jiro2'
TypedDictのオプショナルな引数について
まず、TypedDictには2通りの書き方があります。
以下の例の場合、MovieAとMovieBは同じ意味の型として利用できます。
from typing import TypedDict
MovieA = TypedDict('MovieA', {'name': str, 'year': int})
class MovieB(TypedDict):
name: str
year: int
cat: MovieA = {'name': 'cat', 'year': 1993}
dog: MovieB = {'name': 'dog', 'year': 2000}
では、Movieという型にauthorという属性がある場合とない場合がある場合を考慮したい場合はどうしたらいいでしょうか?
その場合は以下のように書きます。
from typing import TypedDict
class MovieRequiredType(TypedDict):
name: str
year: int
class MovieOptionalType(TypedDict, total=False):
author: str
class MovieType(MovieRequiredType, MovieOptionalType):
pass
# MovieTypeはauthor属性があってもなくても良い
rabbit: MovieType = {'name': 'rabbit', 'year': 2002}
deer: MovieType = {'name': 'deer', 'year': 2006, 'author': 'jack'}
もちろん、必須属性であるnameとyearがなければエディタ上でエラーとなります。
TypedDictに関しては2通りの書き方がありますが、多重継承できるという点でclassスタイルの方がメリットがあります。なので、こちらを積極的に利用する方が多いかと思います。
最後に
Pythonにおける型の重要性について理解いただけたのではないでしょうか。型を書くことはめんどくさいと思われるかもしれませんが、複雑なコードになればなるほど、型のありがたさが理解できるのではないかと思います。
最後に少し注意点ですが、Python3.9の記法は一部のツール(mypyなど)で対応されていないので、ご利用の際はご注意ください。(2020年10月時点)