Python って型書けるの?
書けます!!!
Pythonは別にコンパイルするわけではないので、単体で実行前に静的解析ができるわけではないです......
が、サードパーティ制のソフトウェアを活用することでいい感じ(※要検証)にすることができます。
(あと、実は実行時にも書いた型は影響しません)
typing --- 型ヒントのサポート
Pythonの公式ドキュメントを読んでください。
完
......いやまあこれが真理ではあるんですが、簡単に説明しつつ個人的なハマりポイントを紹介していきます。
typing のつけかた
hoge: str = "Hello, World!"
変数の場合、変数名の後に : <型名>
と記述することで型を書くことができます。
def func(hoge: str) -> str:
return "Hello, World!"
関数の戻り値は、後ろに -> <型名>
をつけることで指定できます。
使える型
-
List
やCallable
など、typing.py
に記載された型 -
int
やstr
などの組み込みクラス - 自作のクラス
- 型変数
など (ちなみに上2つは下2つに含まれるので、分ける必要があるかといえばない)
ジェネリクス
ドキュメントを読め
意訳:別の回で話でもしようかな
Ellipsis
ググラビリティがカスな悪い組み込みオブジェクトに ...
というものがあります。
t: Tuple[str, ...] = ("H", "e", "l", "l", "o", "w")
型を省略するときに使う記号で、以下のように実装に(内部的に)利用されています。
if len(params) == 2 and params[1] is ...:
msg = "Tuple[t, ...]: t must be a type."
p = _type_check(params[0], msg)
return self.copy_with((p, _TypingEllipsis))
ハマりどころ
循環インポート
返り値を自分自身にする (メソッドチェーン)
class A:
def func(self) -> A: # エラー
return self
なんかこんな感じのやつを書きたくなることってあるじゃないですか?
fluent interfaceみたいなことしたくなった時とか (こっちはこっちで長くなりそうだから別で作ろう)
ただ、クラス定義内で自身の名前を使うことはできないので、
NameError: name '<type>' is not defined
みたいなエラーになります。(もちろんIDEにも怒られます)
こんな場合には forward-reference という機能を使います。
class A:
def func(self) -> 'A':
return self
文字列として型名を記述することで、解決を後回しにすることができます。 (IDEでは依存関係が解決できた場合、実際に型を記述したときと同じ挙動をします)
相互的に参照を持つ
これはそもそも設計が悪いから見直せ みたいな記事が散見されたので、良くないんだろうなあとは思いつつ、必要なものは必要なので使わせてくれ!みたいな人向け
(代替案的な設計があればぜひコメントください)
from a import A
from b import B
b: B = B()
a: A = A(b)
b.set_a(a)
from b import B
class A:
def __init__(self, b: B):
self.b: B = b
from typing import Optional
from a import A
class B:
def __init__(self):
self.a: Optional[A] = None
def set_a(self, a: A):
self.a = a
このように、A, Bの両方で相手を参照するようなインポートを行おうとすると以下のようなエラーが出ます
ImportError: cannot import name 'A' from partially initialized module 'a' (most likely due to a circular import) (a.py)
これを解決するためには、 typing.TYPE_CHECKING
を利用します
https://docs.python.org/ja/3/library/typing.html#typing.TYPE_CHECKING
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from b import B
class A:
def __init__(self, b: 'B'):
self.b: B = b
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from a import A
class B:
def __init__(self):
self.a: Optional[A] = None
def set_a(self, a: 'A'):
self.a = a
TYPE_CHECKING
は型検査中のみに True
になる (※非保証) 値。
if文などでインポートをチェックすることにより、エラーを回避できる。
ただ、この状態だと Type Hinting に使っている型名が存在しないのでエラーとなる。
NameError: name 'B' is not defined
ため、前述した forward-refarence と組み合わせることにより評価も遅延して解決することができる。
複雑な(再帰的)データを型付けする
{
"type": "or",
"filters": [{
"type": "or",
"filters": [...]
}, {
"type": "range",
"from": 1,
"to": 10
}]
}
なんかこんな感じで再帰的にデータが入りうるような型を作りたい! → 型変数を使いましょう
TypeOp = Dict[str, Union[str, int, List['TypeOp']]]
json_: TypeOp = {
"type": "or",
"filters": [{
"type": "or",
"filters": [...]
}, {
"type": "range",
"from": 1,
"to": 10
}]
}
戻り値がvoid
Python に void
なんてものはない。
def func()
pass
print(func()) # None
Python では return
を省略すると None
が返るので、型も同様に指定すると良い
上限つきワイルドカード型
親クラスを直接書いてやれば動きます
Type.TypeVar
を使うことで無理矢理 Java のときみたいにも書ける (書いてた) んですが、必要はなさそうです。
キャストしたい
isinstance
でチェックすればよしなにしてくれます。
ただ、直接触っても予測変換がでないので一旦正しい型の変数に代入しましょう。
a: Any = 123
if isinstance(a, A):
b: int = a
#参考文献