Python で TypeScript の interface のように辞書オブジェクトの型定義を手軽に行う
Python で辞書オブジェクトを扱うとき、ある辞書の「キーのセットとその型」をひとまとまりで定義しておきたくなることがよくあります。
interface User {
name: string
age: number
address: string
}
type User = {
name: string
age: number
address: string
}
「クラスを作るほどではないけど最低限の束縛やデータのまとまり表現がほしい」という観点で、ちょうど上記のように TypeScript における interface や type のようなものが Python で書けないかと色々試行錯誤してみたので紹介します。
Python に擬似的な静的型付けを導入済みであることは前提とします。
TypedDict をつかう
標準モジュールに型に関する諸々を扱う typing
というものがありますが、その中にある TypedDict
を使います。
インポートはこうです:
from typing import TypedDict
ビルトイン化したのは Python 3.8 からなので、それ以前のバージョンで使いたい場合には PyPl にある typing-extensions
を使用します。
pip install typing-extensions
from typing_extensions import TypedDict
インポートするときは単語の区切り文字が -
ではなく _
であることに注意してください。
PyPl の このページ を見ればわかりますが、Python は 3.6 近辺以降で型に関するアップデートが急増しています(作者が積極的に Python に型による堅牢性を導入しようとしているとのこと)。そのほとんどの機能が typing-extensions
ではサポートされています。
後方互換性がどのバージョンまで保たれているかは明記がないのですが、それは「しばらくすると少しずつこれらの機能は廃止されていきます」という説明からも動的であることが理由のように思えます。
つかいかた
利用方法はとても簡単で、以下のようにするだけです。とても感覚的。
class User(TypedDict):
name: str
age: int
address: str
これはもちろん普段のプリミティブな型やクラスと同様の型アノテーションとして使用することができ、例えば引数の受け渡し時などに効果を発揮します。
def logIn(user: User) -> None:
...
ちなみに、いわゆる「Optional 指定」も可能で、こういう書き方もできます:
class User(TypedDict, total=False):
name: str
age: int
address: str
ただしご想像の通り、特定のキーだけを Optional にする方法は(今のところ僕は)知りません。あったら教えてください。
その他、辞書オブジェクト名を入力したときその後に続くキー名の補完が効くのがかなり素敵です。こういう小さなイベントが開発のモチベーションを上げてくれますよね(たぶんそういう意味では「小さく」はない)。
@dataclass について
Python 3.7 以上であるならば dataclass
というものを使うことで上記と似た挙動を実現できますが、これは使用のたびにクラスを生成するような作業が必要になるため「単なる辞書を扱いたい」というニーズからはズレ始めます。
@dataclass
class User:
name: str
age: int
address: str
print(User("mirumi", 3, "Mofu"))
print(User(name="mirumi", age=3, address="Mofu"))
常用したことがないのでこれについては終わりにしますが、「似たようなものがある」という情報として残しておくことにします。
タプルの場合
本記事のメインの趣旨とはこれも少し違うものですが、タプルでも同じようなことが可能です。
from typing import NamedTuple
class User(NamedTuple):
name: str
age: int
address: str