今回は自分の勉強も兼ねてPythonの型ヒントについて簡単にまとめました。
動作バージョン、Python 3.8.6
前提として、Pythonは型を定義しなくても動きます。
正直に言うと型を定義するのは面倒ですし、指定しないでも動くのがPythonの良いところだとも思います。
ただし、保守性、可読性などを考えると、Pythonでも型を意識した方が圧倒的にいいです。
例えば適当な関数を用意しました。
def add_func(a, b):
return a + b
説明するまでもないですが、二つの引数を足して返すだけの関数です。
これに型ヒントをつけて、整数を受け取ってほしい場合にはこうします。
def add_func(a: int, b: int) -> int:
return a + b
a: int
の部分が引数の型、-> int
の部分が返り値の型です。
使ってみましょう。
some_value = add_func(a=5, b=2)
print(some_value)
>>>
7
問題なく動作します。
次に試しに文字列を渡してみましょう。
some_value = add_func(a="John", b="Lennon")
print(some_value)
>>>
JohnLennon
直感的に言って驚くべき部分ですが、Pythonは型ヒントと矛盾する形で引数を渡してもエラーになりません。
とはいえ、全く意味がないかというとそうではありません。
例えば今どきのIDEですと、関数を使おうとした際に、型ヒントを表示してくれます。
int型を指定するんだな、ということが分かります。
試しに無視して、文字列を入れてみましょう。
警告を表示してくれました。
引数にint型が指定されてるのに、str型が指定されてるから直せよって教えてくれるわけです。
このように型ヒントを付与することで、関数が意図しない形で使われる危険を減らすことができそうです。
上記に加えて、mypy
というモジュールを使うと事前に型のテストをすることが可能です。
pip install mypy
使うのは簡単で、例えば以下のような型エラーが発生しているコードをわざと書いてみます。
# type_test.py
def add_func(a: int, b: int) -> int:
return a + b
add_func('str', 'str')
次にターミナルからtype_test.py
を呼び出します。
mypy type_test.py
>>>
type_test.py:6: error: Argument 1 to "add_func" has incompatible type "str"; expected "int"
type_test.py:6: error: Argument 2 to "add_func" has incompatible type "str"; expected "int"
Found 2 errors in 1 file (checked 1 source file)
このようにエラーを検出してくれました。
次に公式の型ヒントライブラリtyping
を参考に、型ヒントの使い方をまとめていきます。
まずはシンプルな型です。
# intを引数
def add_func(a: int, b: int) -> int:
return a + b
# strを引数
def fullname_func(last_name: str, first_name: str) -> str:
return f'{last_name} {first_name}'
場合によっては引数に特定の値を指定したいケースがあるかもしれません。
そういう場合はLiteral
を使用します。
from typing import Literal
def greeting(language: Literal["python", "javascript"]) -> None:
print(f'Hello, {language}')
引数のlanguageにpython
もしくはjavascript
を強制するような形です。
試しにruby
を指定すると警告が出ます。
数値を指定する場合。
def accepts_only_four(x: Literal[4]) -> None:
pass
こういう場合はUnionを使います。
例えばある掛け算をする関数に、int型、もしくはfloat型で受け取りたいような場合。
from typing import Union
def multiplication_func(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
return a * b
# ok
some_value1 = multiplication_func(1, 1.2)
some_value2 = multiplication_func(2, 2)
some_value3 = multiplication_func(2.2, 2.2)
ちなみに型は変数っぽく使うこともできます。
from typing import Union
MyUnionType = Union[int, float]
def multiplication_func(a: MyUnionType, b: MyUnionType) -> MyUnionType:
return a * b
from typing import Union, List, Tuple, Dict
def list_sum_func(numbers: List[Union[int, float]]) -> Union[int, float]:
"""
intもしくはfloatで構成されたリストを受け取る
"""
return sum(numbers)
def tuple_full_name_func(name: Tuple[str, str]) -> str:
"""
(str, str)のタプルを受け取る
"""
return f'{name[0]} {name[1]}'
def dict_person_profile_func(profile: Dict[str, str]) -> str:
"""
キーと値がstrの辞書を受け取る
"""
return f'{profile.get("name")}さん {profile.get("age")}'
list_sum_func([1, 2, 3.1])
# >>> 6.1
tuple_full_name_func(("柴田", "聡子"))
# >>> 柴田 聡子
prof = {"name": "柴田聡子", "age": "36歳"}
dict_person_profile_func(prof)
# >>> 柴田聡子さん 36歳
上記の辞書の例で年齢のところは36
と整数を入れたいところです。
こういう風にキーと値が統一されていない、ちょっと複雑な辞書を指定する場合はTypedDict
を用います。
from typing import TypedDict
Profile = TypedDict("Profile", {"name": str, "age": int})
def dict_person_profile_func(profile: Profile) -> str:
"""
Profile型を受け取る
"""
return f'{profile.get("name")}さん {profile.get("age")}歳'
prof = Profile(name="柴田聡子", age=36)
# >>> {'name': '柴田聡子', 'age': 36}
dict_person_profile_func(prof)
# >>> 柴田聡子さん 36歳
TypedDictよりももう少し複雑な型を指定したい場合はdataclassの使用を検討した方がいいでしょう。
例えば先ほどのProfileの例と同じようなことをする場合。
from dataclasses import dataclass
@dataclass
class SingerProfile:
name: str
age: int
def singer_profile_func(profile: SingerProfile) -> str:
return f'{profile.name} {profile.age}歳'
prof = SingerProfile(name="カネコアヤノ", age=29)
print(prof)
# >>> SingerProfile(name='カネコアヤノ', age=29)
print(singer_profile_func(prof))
# >>> カネコアヤノ 29歳
クラスにすると、少し複雑な型も指定がしやすくなります。
例えばあるシンガーと紐づく複数のアルバムを情報として持ちたい場合。
from dataclasses import dataclass
from typing import List
@dataclass
class Album:
title: str
price: int
@dataclass
class SingerInformation:
name: str
age: int
albums: List[Album]
def show_singer_data(data: SingerInformation) -> None:
"""SingerInfomationを受け取り、情報を表示する"""
print('===Profile===')
print(f'name: {data.name}')
print(f'age: {data.age}')
print('===Album===')
for album in data.albums:
print(f'title: {album.title}')
print(f'price: {album.price}')
albums = [
Album(title="しばたさとこ島", price=1919),
Album(title="いじわる全集", price=3018)
]
singer_information = SingerInformation(name="柴田聡子", age=36, albums=albums)
show_singer_data(singer_information)
# >>>
# ===Profile===
# name: 柴田聡子
# age: 36
# ===Album===
# title: しばたさとこ島
# price: 1919
# title: いじわる全集
# price: 3018
今回はPythonの型ヒントについて簡単にまとめました。
型ヒントをしっかりと書くと、保守性や可読性の向上もさることながら、何となくコードがかっこよく見えるというメリットもありますね!
引き続き意識していきたい部分です。