エンジニアの頭の中

知識と技術で「お金稼ぎの自動化」を実現するため、日々奮闘するエンジニアのブログです。

型を明示してPythonコードの品質を向上|Python 3.5で追加された型ヒント(Type Hints)

f:id:mitsu3204:20180819161005j:plain

Python 3.5から追加された型ヒント(Type Hints)という機能をご存知でしょうか?もしくは、知っていても活用しているでしょうか?

型ヒントを使用することによって、コーディングがより便利に、かつ保守性を高めたり、不具合リスクを減少させることができます。私は積極的に使用するようにしており、2系のPythonと比べると、型ヒントによって随分とコードを読むのが楽になったと思います。

そんなPythonの型ヒントについて、簡単にまとめてみました。

型ヒント(Type Hints)ってなに?

型ヒント(Type Hints)とは、Pythonソースコード上に、型の情報を加えるものです。

PEP 484 — Type Hints | Python.orgには、以下のコードが示されています。

def greeting(name: str) -> str:
    return 'Hello ' + name

関数greetingの引数であるnameの後ろに: strという記載があります。これによって、この引数nameの型が、strであることを示しています。

また、引数の宣言の後から、:までの間に、-> strという記述がありますが、こちらは、この関数の戻り値に対する型ヒントであり、上記のコードの場合、戻りの型がstrであることを示しています。

このように、ソースコード上の関数の引数、関数の戻り値、変数などに対して、その 戻り値や変数などが何の型であるかの情報を付加する のが、型ヒントということです。

型ヒント(Type Hints)を使う意味

型ヒントによる型の指定は、Pythonプログラムの実行に対して、強制力を持つわけではありません。

例えば、サンプルのgreeting関数のstrの型が指定されたnameに対して、数値型の値を指定することもできてしまいます。

型ヒントを使うメリットには、以下のようなものが考えられます。

静的解析による型チェックが可能になる

静的解析による型の把握が可能になるため、コードを実行しなくても、型のチェックや、コーディングの際に型特有のプロパティの入力補完などが実現可能となる。

例えばIDEの場合、コード上で型が明示されることによって、IDEが型を認識して、該当のクラスのメソッドを入力補完機能で表示できるようになるなどが挙げられます。

/型ヒントが指定されていない場合/ f:id:mitsu3204:20180819161103p:plain

/型ヒントが指定されている場合/ f:id:mitsu3204:20180819161113p:plain

nameがstr型としてヒントが指定されているので、入力補完機能によって、str型のオブジェクトが持つメソッドが、候補としてリストアップされます。これはコーディングが捗りますね。

可読性が上がる

Pythonソースコード上で型が明示されているため、人がソースコードを見た際の可読性が上がります。

型ヒントがなかった時のPythonは、変数や関数を扱う際に、型を確認するのが面倒な場合がありました。

IDEを使っていても、型を把握できるのは限定された状況のみだったので、docを見て型の記載がないと、コードを読む必要がありましたが、宣言部分から一目で判断できる or IDEが自動的に把握可能になったので、かなり楽になったと思います。

型ヒント(Type Hints)の書き方

型ヒントの基本的な書き方です。

関数の引数

関数の宣言部で、引数名の後ろに: 型名と記述することによって、引数の型ヒントを指定します。

def say(word: str):
    print(word)

関数の戻り値

関数の引数の宣言の後から:までの間に、-> 型名と指定します。

def add(a: int, b: int) -> int:
    return a + b

変数

関数の引数と戻り値だけでなく、通常の変数にも型を指定できます。末尾に#コメントで、type: 型名の形式で型を指定します。

a = 'Hello'  # type: str

リスト、辞書、タプル

typingというモジュールにListDictTupleなどが定義されています。コレクションの要素の型まで指定する場合は、これらを使用します。

from typing import List, Dict, Tuple

l = ['a', 'b']      # type: List[str]
d = {'age': 18}     # type: Dict[str, int]
t = (True, False,)  # type: Tuple[bool, bool]

型ヒントによる様々な表現

型ヒントには、いろいろな使いかたがあります。一部を紹介します。

Type alias - 型の別名

型ヒントは、エイリアスを作ることができます。

Name = str

def add_user(name: Name, age: int) -> None: ...

Nameという型ヒントを定義して、add_user関数の引数であるnameに適用しています。

エイリアスを定義する際は、ユーザーの独自クラスを定義する時と同様に、その名称の先頭は大文字とするべきとPEPには記載されています。

エイリアスは、複雑な型を単純化して表現する際に有効のようです。 例えば、以下のようなコードがあったとします。

from typing import List, Dict

def send_message(List[Dict[str, str]]) -> None:
    ....

これは、型エイリアスを使うと以下のように表現することも可能です。

from typing import List, Dict

Address = str
Message = str
To = Dict[Address, Message]

def send_message(to: List[To]) -> None:
    ....

Callable - 呼び出し可能であることを示す型ヒント

呼び出し可能なオブジェクト、つまり関数オブジェクトを期待する場合に、Callableという型ヒントを使用することができます。

from typing import Callable

def call(func: Callable[[str, int], str]) -> None:
    ....

Callableを使用する時は、関数のシグネチャを指定することができます。

上記の例では、関数callの引数であるfuncの型として、第一引数にstr、第二引数にint、戻り値がstrシグネチャを持った関数を指定しています。

NewType - 独自のクラスを定義

型ヒントのための独自のクラスを作るために、NewTypeという関数が用意されています。

from typing import NewType

UserId = NewType('UserId', int)

def func(user_id: UserId) -> str:
    ....

NewType関数によって、UserIdという型を作成しています。 これは、intのサブクラスの位置付けとなるもので、以下の定義と同様です。

class UserId(int):
    pass

UserIdの型ヒントが指定されているところに、intを渡すと、静的検査としては、チェックエラーとなります。

# これはNG
s = func(12345)

# これはOK
s = func(UserId(12345))

ただし、実行時にはなんら制限はありませんので、intを直接渡しても、問題なく動きます。

Generics - ジェネリクス

ジェネリクスも対応しています。Tのように任意の型であることを指定できます。PEPのサンプルコードを見てみましょう。

https://www.python.org/dev/peps/pep-0484/#generics

from typing import Sequence, TypeVar

T = TypeVar('T)

def first(l: Sequence[T]) -> T:
    return l[0]

この場合、関数firstの引数lの要素と戻り値は、同じTという型ヒントを指定しており、一貫性のある型であることを示しています。

つまり、引数lの要素がstr型だとしたら、戻り値もstr型となるし、引数lの要素がint型の場合は、戻り値もint型であるということです。

また、TypeVarは、起こりうる型を指定することができます。

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

AnyStrは、strbytesのみを許容するという意味になります。

終わりに

ここで紹介したのが、型ヒントの全てというわけではありません。

私も普段書いていると言っても、基本的な使い方のみで、ドキュメントに記載されている全ての書き方を試したわけではありませんので、もし興味があるかたは、ぜひドキュメントを読んで試してみてください。

Pythonは今でも2系がよく使われているイメージがありますが、バージョンが上がるにつれて、型ヒントのようにどんどん便利な機能も追加されていっているので、積極的に新しいバージョンを採用していきたいですね。

最後に、個人的にPython初級者の学習にオススメの本の紹介です。絵が豊富で、理解と記憶がしやすいように書かれた本です。

Head First Python 第2版 ―頭とからだで覚えるPythonの基本