198
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

実践!!Python型入門(Type Hints)

はじめに

みなさん、PythonのType Hints使ってますか?
Pythonは動的型付き言語なので、明示的に型情報をつけずにコーディングできることがメリット、と感じされている方もいらっしゃるかもしれません。
その意見はもっともなのですが、型を明示的に指定することで、よりバグを発生させにくいコーディングができることも事実かと思います。
本記事ではPythonの型についての入門~中級の内容を扱います。
皆さんのPythonスキルアップの一助となれば幸いです。

注意事項

Pythonの静的型解析にはmypyというライブラリが必要ですが、mypyの使い方、インストール方法などについては解説しません。

ステップ1:基本的な変数の型[イミュータブル]

変数の型の書き方は下記の通りです。

<変数名>:<型>
<変数名>:<型> = <初期値>

実際に使う場合は下記のようになります。

test: int = 12
test2: bool = True
test3: str = 'Hello'
test4: float = 1.12

test = 'hello'  # mypyエラーが出る

testという変数をintで定義しているのに、文字列を代入しようとすると、mypyのエラーが発生します。
mypyエラーが発生しますが、コード自体は問題なく実行可能です。

ステップ2:基本的な変数の型[ミュータブル]

次は、辞書や配列の型について見ていきます。

test5: list = [1, 2, 3]
test6: dict = {'name': 'taro'}

test5 = 12 # mypyエラーが出る
test6 = 'Name' # mypyエラーが出る

test5を配列として、test6を辞書として定義しているので、それ以外の値を代入しようとすると、mypyエラーが発生します。
上記の型定義でもいいのですが、配列と辞書についてはtypingのListとDictの使用を推奨します。
(Python 3.9から非推奨ですのでご注意下さい)

from typing import List, Dict

test7: List[int] = [1, 2, 3] # intの配列のみに絞ることができる
test8: Dict[str, str] = {'name': 'taro'} # 辞書のキーと値をstrのみに絞ることができる

test5とtest6の場合は、配列か辞書かどうかという型の定義しかできません。
ですが、test7とtest8の場合は、intの配列、キーと値がstrの辞書、というようにより詳細な型の指定が可能です。
基本的にはこちらの使用を推奨します。

ステップ3:複数の型を許容する場合

ある変数にintとstrを許容したいという場面は多くあるかと思います。
その場合は、Unionを使います。

from typing import Union

test9: Union[int, str] = 10 # intとstrを許可する
test9 = 'Name' # OK
test9 = bool # mypyエラーが出る 

test9はintとstrを許可する変数として定義したため、boolの値を代入しようとするとmypyエラーが発生します。

ステップ4:関数の型

基本形

関数の型の基本形は下記になります。

def <関数名>(<変数名>:<型>) -><戻り値の型>:
    ...

実際に使う場合は以下のようになります。

def add(x: int, y: int) -> int:
    return x + y

返り値がない関数の場合

関数の返り値がない場合も良くあります。
その場合は、返り値の型をNoneとして設定します。

def log_print(number: int) -> None:
    print('値は{}です'.format(number))

b = log_print(12) # mypy error

返り値がない関数の返り値を変数bに代入しようとすると、mypyエラーが発生します。

ステップ5:Noneを許容する場合はOptionalを使う

辞書に対してgetメソッドを使うと、その辞書内に該当のキーが存在する場合は対応する値を返し、キーが存在しない場合はNoneを返します。
その場合、値+Noneを許容する型をUnionで定義しても良いのですが、Optionalを使うとコードをすっきり書くことができます。

from typing import Optional

test10: Dict[str, str] = {'name': 'taro'}
test11: Optional[str] = test10.get('name') # str+Noneを許容する
test11 = test10.get('age') # Noneが返る

上記の場合は、test11はstrとNoneを許容する型として定義することができます。

ステップ6:より詳細な辞書型の定義をする[TypedDict]

コーディングにおいて辞書型は多用するかと思います。
typingのDictを使うとキーと値の型をより詳細に絞ることができることはすでに紹介させて頂いたのですが、さらに詳細に絞ることが可能です。

from typing import TypedDict

Movie = TypedDict('Movie', {'name': str, 'year': int})

movie: Movie = {'name': 'Blade Runner', 'year': 1982}
movie['age'] = 10  # mypy error
print(movie.get('author'))  # mypy error

上記のように書くことで、Movieという型はnameというキーの値はstrで、yearというキーの値はintである、という型定義が可能になります。
当然定義されていないキーでのデータ追加や参照を試みた場合は、mypyエラーになります。

TypedDictには2通りの書き方が存在しており、上記の書き方ではなくClass形式の書き方の方がメリットがあります。
詳細は下記を参照ください。
Pythonで型を極める【Python 3.9対応】

※上記の参照方法from typing import TypedDictはPython3.8以降でのみ有効です。
それより前のバージョンの場合はfrom typing_extensions import TypedDictを使用してください。

ステップ7:Classの型定義

クラスに関してはこれまでの説明以上のことはありません。
メンバ変数やメンバ関数に型をつけることが可能です。

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    student_number: str

    def print_information(self) -> None:
        print('Name: {}, Student Number: {}'.format(self.name, self.student_number))


taro = Student('taro', '1234')
taro.print_information()
print(taro.age)  # mypy error

上記のコードについては、当然Studentクラスにはageというメンバ変数が定義されていないので、エラーになります。

ステップ8:Anyについて

Anyをつけておくと型の整合性が無視されます。

from typing import Any

# Anyをつけることであらゆる型を受け入れる変数として定義可能
test12: Any = {'name': 'taro', 'age': 12} 
test12 = 'Hello' # No mypy Error

Anyを使うとその変数にどんな値でも代入できるというだけでなく、様々な場面で弊害が発生します。

from typing import Any
test13: int = 10
test13 = test12  # Anyで定義された変数はどの変数にも代入可能

def sub(x: float, y: float) -> float:
    return x - y

test14: Any = 'Hello'
sub(test14, 12.1)  # 本来文字列が入るはずがないのに文字列を許可してしまう

Anyで変数を定義すると、その変数を別の変数に代入する際、関数の引数として利用する場合などの型チェックが無視されます。
なので、想定外の挙動が起きる可能性があるため、Anyの使用は厳禁です。
Anyを使うくらいなら型定義を書かない方がよいかと思います。

おまけ:型エイリアス

使用頻度はあまりありませんが、型に別名(エイリアス)をつけることも可能です。

# 型に別名をつける
UserData = Dict[str, Union[str, List[Dict[str, str]]]]

hanako: UserData = {
    'user-id': '1234',
    'belongings': [{'id': '12', 'name': 'PC'}, {'id': '23', 'name': 'Tablet'}],
}

最後に

本記事では基本から中級レベルの内容を取り扱いました。
今回取り扱った内容以外にもさらに難しい内容もあるのですが、普通にコーディングする際には今回扱った内容で十分ではないかなと思います。
Pythonの型についてはmypyのリファレンスが詳しく分かりやすいのでそちらを参照されることが良いかと思います。
mypy Type hints cheat sheet
少しでも皆さんのコーディングの助けになれば幸いです。
では(^▽^)

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
198
Help us understand the problem. What are the problem?