Pythonに訪れる型のある世界
1. はじめに
この記事のターゲット
Pythonで中規模以上の開発をしたいが、コードの可読性に悩んでいる方
はじめに
Pythonは何よりも美しいと信じてやまない、エンジニアの眞嶋です。
美しいコードとはなんぞや、わかりやすさとはなんぞや、そんなことを考えながら飲むコーヒーの味は格別ですね(きっと皆も)
さて、今回は「型のある世界」というテーマで書いていこうと思います。
動的型付け言語って便利な反面、複数人で開発していると「このメソッドって何が欲しいの?」とか「これって配列?連想配列?」なんて疑問がわくこともしばしば。
最近はC#にどっぷりと浸かったこともあり、型がないのが気になるお年頃になったので新しい世界を切り開くことにしましょう。
2. アノテーション
アノテーション(Annotation)という言葉に聞き覚えはありますか?
IT用語としては、自然言語処理や人工知能の開発をする際、機械学習の学習データとして使用する「タグ付きデータ」のタグのことを指すことが多いですね。
アノテーションという言葉は、「注釈」という意味を持っています。
つまり、「Pythonの型のある世界」とは「型について注釈を入れ、ローカルルールを形成する」ということになります。静的型付け言語化する、という訳ではなく、あくまで動的型付け言語にコーディングレベルでの型の概念を実装するための手法といえます。
アノテーションの基本
まずは基本的な記述からご紹介したいと思います。
def sum(x: int, y: int) -> int:
sum = x + y
return sum
値を2つ渡して、その合計を返すメソッドを例に考えてみましょう。
データの型を表すには「: <型名>」と記述します。
また、メソッドの戻り値を表す場合は「-> <型名>」と記述します。
基本的にはこれだけのことなのですが、複雑なメソッドやクラスレベルになってくると、アノテーションがあるかないかで可読性に大きな差が生まれてきます。
アノテーションって本当に便利なの?
こんな簡単なもので本当に便利なの?という疑問はごもっともです。
僕も初めて見たときは「こんなのなくてもわかるし良くない?」と思ったのは、まだ記憶に新しいことです。
処理の共通化や分離を行って初めて実感する幸福を、簡単に感じていただくためにちょっとしたサンプルをご用意しました。
アノテーションなしの場合
from datetime import datetime
from dateutil import parser
class DateTimeUtility:
@staticmethod
def to_string(date_module, is_date_only = False):
if (date_module is None):
return ''
converted = date_module.strftime(
'%Y-%m-%d' if is_date_only else '%Y-%m-%d %H:%M:%S'
)
return converted
@staticmethod
def to_datetime(date_string, default = None):
try:
converted = parser.parse(date_string)
except:
converted = default
finally:
return converted
アノテーションありの場合
from datetime import datetime
from dateutil import parser
class DateTimeUtility:
@staticmethod
def to_string(date_module: datetime, is_date_only: bool = False) -> str:
if (date_module is None):
return ''
converted = date_module.strftime(
'%Y-%m-%d' if is_date_only else '%Y-%m-%d %H:%M:%S'
)
return converted
@staticmethod
def to_datetime(date_string: str, default: datetime = None) -> datetime:
try:
converted = parser.parse(date_string)
except:
converted = default
finally:
return converted
日付データの変換などを行うUtilityを作成した場合、そのメソッド内には「datetime型」と「str型」が混在することになります。
また、中には処理方法を切り替えるために別の値が渡されるなどデータ型がわからないと内部のコードを解読する必要があり厄介ですね。
その点、アノテーションが記載されていると、どういうデータを渡せば良いかが一目瞭然となり、可読性が非常によくなることが分かります。
こんなのが50個あるといかに読みやすいことか…。アノテーションありがとう!
3. メモだけじゃないアノテーションの使い道
さて、冒頭お話しした通り、アノテーションは非常に便利ですが、これだけでは可読性を高めてエンジニアの開発効率を向上させる程度の効果しかありません。(とても嬉しいことですが)
実際にアノテーションを「意味のあるもの」とするには、やはり品質に対してのアプローチが必要になってきます。
そして、アノテーションはさらなる進化を遂げ、品質維持にまで貢献してくれるようになりました。
実装とアノテーションのギャップ
アノテーションとはコーディングをする上でのルールだというお話はしましたが、中にはルールを守らなくてもエラーにならない、だけど結果はおかしいという事態は多々発生するのが動的型付け言語の厳しいところです。
例えば先に使用したSum関数を例に見てみましょう
from decimal import Decimal
value1 = Decimal('1.00')
value2 = Decimal('2.00')
print(Sum(value1, value2))
def sum(x: int, y: int) -> int:
sum = x + y
return sum
このような使い方をされたとき、プログラムはエラーを出すことはありません。
しかし、処理の結果を見てみるとどうでしょうか?
> 3.00
想定しているのとは違う出力がされてしまっていますね。
本来であれば「3」という出力を期待していましたが、Decimal型を使用したことによって「.00」という余計な出力が生まれてしまいました。
データとして使用するのみであれば大きな影響はありませんが、これが画面出力に使用している処理であればバグ認定は確実です。
これを避けるためにアノテーションに沿ったコーディングがされているかをチェックすることができます。
mypyの導入
アノテーションを守ったコーディングが出来ているかをチェックするライブラリ「mypy」をご紹介します。
1.mypyのインストール
インストールはpipから簡単に行うことができます。
> pip install mypy
これだけで導入できるので楽ちんですね
2.mypyでの型チェック
ここからが大本命の型チェックの方法になります。
といってもやることは簡単で、以下のコードをコマンドプロンプトやターミナルから実行するだけでOKです
mypy sample.py
すると結果として以下のようなものが返ってきます
sample.py:6: error: Argument 1 to "sum" has incompatible type "Decimal"; expected "int"
sample.pyの6行目で「sum」関数にDecimal型を渡しているけど、int型で定義されてるよというエラーが出ましたね。
これをもとに修正することで、メソッドを意図した通りに扱うことができるので、ぜひ単体テストの一環として使ってみてください!
4. アノテーションに使える型
アノテーションに使用できる型の一覧と、拡張型の参考を記載しておくので、ぜひ試してみてください。
アノテーション型抜粋
他にもtypingモジュールではAnyやIterableなどのいろんな型があるので調べてみてくださいね。
ちなみに、上記はPython3.8までの記法になっており、Python3.9以降では組み込みモジュールでサポートされている型が増えていたり、typingモジュールをインストールしなくても、別の組み込みモジュールからインポートできたりするので、ご自身のバージョンを見て記法を選択していきましょう。
5. さいごに
最後までお読みいただきありがとうございました。
ちょっとした作業効率化であれば静的型付け言語の方が便利だとよく言われますが、Webアプリケーションを作るとなると型がないから保守が大変。
そんな悩みを少しでも軽減できるようにと、今回の記事を書いてみました。
以上、Pythonをこよなく愛する眞嶋がお送りしました。