PythonのType Hintsとmypy

(2018-09-30)

動的型付け言語であるPythonで型アノテーションを書けるようにするための構文。 PEP 484で提案され、Python 3.5で実装された。 実行には影響せず、mypyのようなツールで静的解析したりするために使われる。

mypyをインストールする。

$ python -m pip install -U mypy

以下のように引数と返り値に型を書くと、型が誤っている場合にmypyで検知できるようになる。

$ cat main.py
def foo(n: int) -> str:
  return str(n)

print(foo(3))
print(foo('3'))

$ python main.py
3
3

$ mypy main.py
main.py:5: error: Argument 1 to "foo" has incompatible type "str"; expected "int"

また、Type HintsがないライブラリなどのためにStubファイルを別に作って型を書くこともできるようにもなっている。デフォルトではStubがないモジュールはエラーになってしまうので必要に応じてignore_missing_importする。 mypy.iniやsetup.cfgに設定を書くと自動で使われる

$ cat main.py
import numpy as np

$ mypy main.py
main.py:1: error: No library stub file for module 'numpy'
main.py:1: note: (Stub files are from https://github.com/python/typeshed)

$ vi mypy.ini
[mypy]
[mypy-numpy]
ignore_missing_imports = True

VSCode上でもmypyを有効にすると表示されるようになる。FormatterやLintの設定と併せて有効にしておくとよい。

PythonのLintとFormatter - sambagiz-net

"python.linting.mypyEnabled": true

VSCodeでの表示

Callable

Callable[[引数], 返り値]のように書く。引数を…にすると返り値だけをチェックさせることができる。

from typing import Callable

def foo(f: Callable[[int], None]) -> None:
  f(1)

def bar(f: Callable[..., None]) -> None:
  f(1)

def f1(x: int):
  print(x)

def f2(x: str):
  print(x)

foo(f1) # ok
foo(f2) # error: Argument 1 to "foo" has incompatible type "Callable[[str], Any]"; expected "Callable[[int], None]"
bar(f1) # ok
bar(f2) # ok

Generics

from typing import Dict
from typing import TypeVar, Generic

T = TypeVar('T')

class Foo(Generic[T]):
  def __init__(self, v: T) -> None:
    self.v = v
  
  def getV(self) -> T:
    return self.v

m: Dict[str, Foo[int]] = {'a': Foo(1)}
m2: Dict[str, Foo[str]] = {'a': Foo('1')}

def bar(m: Dict[str, Foo[int]], key: str) -> Foo[int]:
  return Foo(m[key]).getV()

def bar2(m: Dict[str, Foo[int]], key: str) -> Foo[str]:
  return Foo(m[key]).getV() # error: Incompatible return value type (got "Foo[int]", expected "Foo[str]")

bar(m, 'a')
bar(m2, 'a') # error: Argument 1 to "bar" has incompatible type "Dict[str, Foo[str]]"; expected "Dict[str, Foo[int]]"

TypeVarはTypeVar('T', int, float)のように取り得る型を指定したり、 TypeVar('T', bound=Employee)のようにupper boundを指定する(そのサブクラスが取り得る型)こともできる。 また、取った型はデフォルトでinvariant(非変, そのスーパークラスもサブクラスも許容しない)として扱われるが、 covariant=Trueにするとcovariant(共変, サブクラスは許容する)として扱うことができる。

Union types

いずれかの型を取るUnion typeも書ける。

from typing import Union, Sequence

def foo(e: Union[int, Sequence[int]]) -> Sequence[int]:
    if isinstance(e, int):
      return [e]
    return e

print(foo(1))
print(foo([1]))

Union[T, None]はOptional[T]と同じ。Noneをデフォルト引数にすると自動でOptional[T]として扱われる。

def foo(e: int = None):
    if e:
      print(e)
foo(1)
foo()