【Python】プリミティブな型をちょっとリッチに拡張する
きっかけ
今の案件で基本的にはintの振る舞いで構わないんだけど、 その数値の持つ意味をコードに反映したいということになった。
たとえば、型で具体的に何の値を指しているのかをアノテーションをしたり、ちょっとした変換処理やバリデーションや制約を数値型に実装したり、メソッドを生やしたり。
基本的にはintの振る舞いで構わないというのがミソ
なのでがっつりクラスを作るようなことはしたくなかった。
組み込みライブラリを使う
typingライブラリのNewType
はintやstrといったプリミティブな型に名前をつけることができる。
from typing import NewType UnixTime = NewType("UnixTime", int)
これで、UnixTimeという名前でintと全く同じ振る舞いをする型を生成できる。
型で具体的に何の値を指しているのかをアノテーションするのに役立つ。
バリデーションや制約をつける
上記のNewTypeはバリデーションとかができない。
じゃあどうするか
Pythonのintやstrは中身はclassである。
なので継承して、新たな型を作りだすことが可能
たとえば、
from datetime import datetime class UnixTime(int): def __new__(cls, value): # vlaueをゴニョゴニョする処理 ... return super().__new__(cls, value) def to_datetime(self) -> datetime: return datetime.fromtimestamp(self)
これでinstance化の時にバリデーションしたり、その付加した意味に適したメソッドを生やすことができる。
__new__
を使っているのは、intやstrなどの不変な型(≒プリミティブっぽいやつ)を作成するには__init__
は遅すぎるため。
__new__
はオブジェクトを生成する前に実行される。