Pythonの型定義の方法とは?型ヒントについてもわかりやすく解説

Pythonの変数の型って何に使うの?
Pythonの型の定義の仕方って?
型アノテーションって言葉を聞いたけど何なのかわからない

このような型に関する疑問がある方もいるのではないでしょうか?

こんにちは、ライターのフクロウです。Pythonのインストラクターもやっています。

この記事ではPython初学者に向けて、型の定義の仕方の復習と、型ヒント(型アノテーション)の使い方の基礎を紹介します。Pythonは動的型付け言語ですが、型情報をあえて書いておくことで、プログラミングが楽になったり、あとから見直したときにドキュメントの代わりになったりと嬉しいことがありますよ。

特に型ヒントは最近よく使われ始めた機能です。他の人の書いたPythonのコードを読むためにも、覚えておいたほうが良いでしょう。

この記事はこんな人のために書きました

  • Pythonの型について復習したい
  • 最近話題の型ヒントについて勉強したい

なお、Pythonの記事については、こちらにまとめています。

目次

自己定義型を作る

Python 3 ではクラスを作ることで、自己定義型を用意する事ができます。クラスの定義の仕方については別の記事でも紹介しましたが、例えば以下のように作成できます。(ここからはJupyter Notebook上で試していきますよ。)

# In [1]:

# クラスの作成
class Person:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
    
    def info(self):
        print("====================")
        print(f"名前:{self.name}\n性別:{self.sex}\n年齢:{self.age}")
        
# インスタンスを作成
p1 = Person("太郎", "男性", 50)
p1.info()

p2 = Person("花子", 60, "女性") # 順番が違う?
p2.info()

このクラスでは名前、性別、年齢をインスタンス変数として持っています。そしてinfoというメソッドがそれらを表示する機能を持っています。このコードを実行すると、以下のような結果になります。

[出力結果]

====================
名前:太郎
性別:男性
年齢:50
====================
名前:花子
性別:60
年齢:女性

良さそうですね。インスタンスを作るところでも、infoメソッドを実行するところでもエラーは発生していません。

ちなみに、ここで作ったクラスが本当に型なのかは、以下のようにして確認できます。

>>> type(int)# すべての型はtype型
type

>>> type(Person) # さっきのクラスもtype型
type

>>> type(1)
int

>>> type(p1)
__main__.Person

型ヒントを使ってみよう

関数アノテーションとはなんだろう

さて、型ヒントについて知るために、まずは関数アノテーションについて覚えておきましょう。

Pythonには関数アノテーション(Function Annotations)という機能があります。これは何かと言うと、関数の引数と返り値(またはその片方だけでもいい)にアノテーションをつける事ができるというものです。※アノテーションは注釈という意味です。

# 関数アノテーションの例

def myfunc(x:"ここには適当な数字を入れよう") ->"返り値はx**2":
    return x**2

引数のアノテーションは、引数の名前の後ろに: Pythonの式 の形で書きます。

返り値のアノテーションは、引数を書く括弧と一番最後の:の間に ->Pythonの式 として書きます。

これらを使っても、Pythonの実行時にはなにか効果があるわけではありません。ただ、注釈を書いておいて参考にすることができるだけです。

型ヒントとはなんだろう

型ヒント(型アノテーション)はこの関数アノテーションを使って、関数の引数と返り値の型を書いておくものです。これも実は、アノテーションなのでPythonはあってもなくても同じ動きをします。

ですがmypyというライブラリを使えば、この型アノテーションを使って、引数に適切な型が入っているかなどをチェックすることができるんです!まずは型ヒントを使って、先程のPersonクラスを直してみましょう。

# In [1]の修正例
class Person:
    def __init__(self, name:str, sex:str, age:int): # ここに注目!
        self.name = name
        self.sex = sex
        self.age = age
    
    def info(self):
        print("====================")
        print(f"名前:{self.name}\n性別:{self.sex}\n年齢:{self.age}")
        
# インスタンスを作成
p1 = Person("太郎", "男性", 50)
p1.info()

p2 = Person("花子", 60, "女性")
p2.info()

型ヒントでは、アノテーション部分に型を書き込みます。strやint、floatのような型を使うことが出来ます。これ以外にも様々な型や、ヒントをより便利につけるための方法が用意されていますが、まずはこれだけ覚えておいてください。

さて、これで関数を使う(ここではクラスのメソッドですが)ときに、どの型を入れればいいか確認できるようになりましたね。これを実行しても、最初と同じ結果になるだけです。

mypyで型チェック

インストール

ここでmypyの登場です。mypyによって型アノテーションを使ってプログラムファイルを静的チェックする(関数が入出力するオブジェクトの型がアノテーション情報とあっているかな?をチェックする)ことができます。

インストールはpipで。

$ pip install mypy

普通に使うには、以下のようにmypyコマンドを使います。

$ mypy test.py

これでもOKなのですが、Jupyter Notebook上で使った方が便利なので、以下のコードをJupyter Notebookの一番上のセルに書いて実行しておきましょう。

このコードはJupyter上でmypyを使うマジックコマンドを提供してくれます。この使い方だと、コマンドは上のコードを実行したNotebookだけで使えるようになります。

このマジックコマンドを気に入ったときは、.ipython/profile_default/startup/ ディレクトリに上のコードをtypecheck.pyという名前で保存しておきましょう。そうすればいつでも使えるようになります。

引数の型をチェック

さて、Jupyter上でmypyを使ってみましょう。先ほどのPersonクラスのセルの先頭行にコジックコマンドを書き込んで実行してみましょう。

%%typecheck --ignore-missing-imports

class Person:
    def __init__(self, name:str, sex:str, age:int):
        self.name = name
        self.sex = sex
        self.age = age
    
    def info(self):
        print("====================")
        print(f"名前:{self.name}\n性別:{self.sex}\n年齢:{self.age}")
        
# インスタンスを作成
p1 = Person("太郎", "男性", 50)
p1.info()

p2 = Person("花子", 60, "女性")
p2.info()

このセルを実行すると、もしも型チェックに引っかかったものがあった場合は以下のように表示されます。

[出力結果]

<string>:17: error: Argument 2 to "Person" has incompatible type "int"; expected "str"
<string>:17: error: Argument 3 to "Person" has incompatible type "str"; expected "int"

このセルの17行目の第二引数と第三引数の型がエラーを出していますね。このようにしてmypyを通した結果を見ることで、期待通りの型になっているかチェックする事ができます。

返り値の型をチェック

また、返り値のチェックについても見ておきましょう。先程のmyfuncを使ってみます。

例えば、引数xが整数型で、返り値が文字列型の関数が作りたかったとしましょう。しかし実際には、整数を受け取って浮動小数点数(float)を返す関数になっているとします。

%%typecheck --ignore-missing-imports

def myfunc(x:int)->str:
    return x**2

[出力結果]

<string>:4: error: Incompatible return value type (got "int", expected "str")

mypyは出力としてstrを期待しているのに、実際に返ってくるのはintだと訴えています。このようにして関数の入出力をチェックできるんですね。あとはこの結果に基づいて、コードを修正すればOKです。

自己定義型でもチェックできる

さて、最後に自分で作った型も型ヒントとして使えることを確認しておきましょう。

Personクラスを定義したセルに、Person型を受け取る関数を追加します。この関数は受け取った引数をprintするだけのものです。

%%typecheck --ignore-missing-imports

class Person:
    def __init__(self, name:str, sex:str, age:int):
        self.name = name
        self.sex = sex
        self.age = age
    
    def info(self):
        print("====================")
        print(f"名前:{self.name}\n性別:{self.sex}\n年齢:{self.age}")
        
# インスタンスを作成
p1 = Person("太郎", "男性", 50)
p1.info()

p2 = Person("花子", "女性", 60)
p2.info()

def myfunc2(p:Person):
    print(p)
    
myfunc2("テスト")

myfunc2(p1)

[出力結果]

<string>:23: error: Argument 1 to "myfunc2" has incompatible type "str"; expected "Person"

====================
名前:太郎
性別:男性
年齢:50
====================
名前:花子
性別:女性
年齢:60
テスト
<__main__.Person object at 0x10d9866a0>

追加した関数はエラーなく動作したことがわかります。ですが、myfunc2はPerson型のために作った関数です。mypyの出力を見ると、ちゃんと引数が期待したPerson型でないことを教えてくれています。

このように、プログラムは通ったが期待した動作ではない場合の発見が簡単にできます。長時間かかる処理をする前にこのようなチェックをしておくと安心できそうですね。

まとめ

この記事では、Pythonにおける型の定義の仕方についてまとめました。型アノテーションは非常に便利な機能だと思います。

この機能はもっと複雑な例のときに効果がわかりやすいでしょう。引数が膨大にある関数や、たくさん関数を作ったときなどを想像してみてください。

次の日には自分の作ったコードすら曖昧になっているかもしれません。そんなときにmypyでチェックできるように型ヒントを書いておけば、コードのバグを簡単に発見する事ができます。

Pythonは動的型付け言語です。それ故に書きやすくて手軽なプログラミング言語なのですが、大きいプログラムを書くときには、このような機能を使って堅実にコーディングすることもできるようになりました。まだ新しい機能で使い慣れてはいないと思いますが、有効な手段として覚えておいて損はないはずです。

この記事を書いた人

第一言語はPythonです。
皆さんRustやりましょう。

目次