39
Python と型ヒント 2015-10-10 Tetsuya Morimoto

Python と型ヒント (Type Hints)

Embed Size (px)

Citation preview

Page 1: Python と型ヒント (Type Hints)

Python と型ヒント

2015-10-10 Tetsuya Morimoto

Page 2: Python と型ヒント (Type Hints)

自己紹介● 森本 哲也

– twitter: @t2y● 白ヤギコーポレーション所属

– カメリオAPI開発 ( 興味がある方はブースへ )● エキスパート Python プログラミング共訳● プログラミング言語歴

– Python (3 年 )– → Java (3 年 )– → Go ( 半年 )

Page 3: Python と型ヒント (Type Hints)

求人● 自然言語処理や機械学習の技術を駆使して製品

開発をしたい人募集!

Page 4: Python と型ヒント (Type Hints)

Pythonのパラドックス● 2004 年 8 月 Paul Graham

– Python を学ぼうとするプログラマーは賢い● Python 2.3.4 (2004-05-27): 2.3 の時代

– 新しい言語を学ぶ人たちはプログラミングが好きであり、仕事に役立つから学ぶわけではない

– 曲解 : 「優秀な Java プログラマーを採用したかったら Python プログラマーを雇えば良い」

● いまは成り立たない

Page 5: Python と型ヒント (Type Hints)

Python3 のパラドックス● 2015 年 10 月

– Python3 を現場に導入しようとするプログラマーは賢い

– Python3 を導入する人たちはプログラミングが好きであり、現場で役立つから導入するわけではない

– 曲解 : 「優秀な Python プログラマーを採用したかったら Python3 を現場に導入すれば良い」

● それっぽい? ( 自己責任で! )

Page 6: Python と型ヒント (Type Hints)

概要● 背景

– 漸進的型付け→関数アノテーション→ mypy● PEP 484 〜型ヒント〜

– 目的– 型ヒントのために定義された型 (Any, Union, etc)– ジェネリクス

● 懸念– 型ヒントの批判、 Typed Clojure の現在

● 展望– 構造的部分型 (Structual Subtyping) へ

Python3.5

Page 7: Python と型ヒント (Type Hints)

背景

Page 8: Python と型ヒント (Type Hints)

背景 : 漸進的型付け (Gradual Typing)

● Jeremy Siek 氏と Walid Taha 氏が 2006 年に考案した型システム

– 静的型付けと動的型付けを組み合わせられる– 双方の型システムの良いとこ取りしたもの– Object 型ではなく Any 型を導入しなければならな

い背景の説明● PEP 484 の理論的背景● 入門記事 : 漸進的型付けとは何か

Page 9: Python と型ヒント (Type Hints)

背景 : 関数アノテーション (PEP3017)

● Python3.0 で導入 (PEP3017)– 2008-12-03 リリース– 関数に任意のアノテーションを書く仕組み

● PEP 484 を支える基本的な仕組み– 関数アノテーションの用途を型アノテーションに

限定した– 関数オブジェクトの __annotations__ 属性に保持

されるのは同じ

Page 10: Python と型ヒント (Type Hints)

背景 :mypy プロジェクト (1)● http://mypy-lang.org/● 作者 : Jukka Lehtosalo 氏

– プログラマーを 6 年、マネージャーを 2 年– 2009年からケンブリッジ大学の博士課程

● 2012 PyCon Finland – MYPY: A PYTHON VARIANT WITH SEAMLESS DYNAMIC AND STATIC TYPING

● 静的型付けと動的型付けの融合– PyPy や Cython がライバル、 GILのない VM

Page 11: Python と型ヒント (Type Hints)

背景 :mypy プロジェクト (2)● 現時点の mypy ができること

– GILのない VM– 型アノテーション→ PEP 484 で標準化– 静的型チェッカーとしての Lintツール

● PEP 484 では型チェッカーは提供しない

Page 12: Python と型ヒント (Type Hints)

PEP 484型ヒント

Page 13: Python と型ヒント (Type Hints)

型ヒント :サンプルコード# -*- coding: utf-8 -*-import jsonimport sysfrom pprint import pformatfrom typing import Any, Dict def pretty_format(data: Dict[str, Any]) -> str: return pformat(data, width=1)

def main() -> None: raw_data = sys.argv[1] # type: str data = pretty_format(json.loads(raw_data)) print(data) data.append('mypy') main()

$ mypy tutorial.py tutorial.py: note: In function "main": tutorial.py:14: error: "str" has no attribute "append"

Page 14: Python と型ヒント (Type Hints)

型ヒント : 目的● 型アノテーションの構文を標準化● 静的解析

– Lintツール ( オフライン型チェッカー )● IDE

– コード補完やリファクタリング機能に利用– Type Hinting in PyCharm

● ドキュメンテーション– 型アノテーションがそのまま使える?

Pythonは動的型付け言語

Page 15: Python と型ヒント (Type Hints)

型ヒント :Sphinx

俺がやる!

Page 16: Python と型ヒント (Type Hints)

型ヒント :Any 型● 全ての型は Any 型のサブタイプ

– object 型と似ているが、型チェッカーによる扱いが異なる

● object 型– 型チェッカーは値のほとんどの操作を拒否する

● Any 型– 型チェッカーは値の全ての操作を許容する

Page 17: Python と型ヒント (Type Hints)

any_and_object.py: note: In function "object_func":any_and_object.py:8: error: Unsupported left operand type for + ("object")

型ヒント :Any 型と object 型from typing import Any def any_func(x: Any, y: Any) -> Any: return x + y def object_func(x: object, y: object) -> object: return x + y

mypy

object 型の操作は型チェカーでエラーとなる理論的背景は「漸進的型付けとは何か」を参照

Page 18: Python と型ヒント (Type Hints)

型ヒント :直和型 (Union Type)● 直和型

– 関数型プログラミング言語でよく使われるデータ型– 典型的にはツリーのデータ構造を簡潔に表現できる

● 単一の引数に対して少数の期待される型の集合をとるe = Union[Employee, Sequence[Employee]]

# OCaml バリアント型 type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat# 第7回「代数データ型」でいろいろなデータを表してみる

Page 19: Python と型ヒント (Type Hints)

型ヒント :Optional 型● 直和型の応用● Union[T1, None]と Optional[T1]は等価

– None を取り得ることが明示される– None は多くの操作が型エラー

● Java の NullPointerException はこりごり– @Nonnull, @Nullable のアノテーションを明示– Collections.emptyList(), Collections.emptyMap()

● Null安全を型システムで保証する世界へ

Page 20: Python と型ヒント (Type Hints)

型ヒント :Optional 型の例from typing import Optional, Union

def func1(data: Optional[int]) -> int: return 3 + data

def func2(data: Union[int, None]) -> int: return 3 + data

def func3(data: int=None) -> int: return 3 + data

これらは等価な型ヒント・デフォルト引数に None を指定したときも Optional 型として扱う

TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

Page 21: Python と型ヒント (Type Hints)

型ヒント : ジェネリクス● コンテナー内の要素に期待する型を示す● TypeVar というファクトリーを使って型変数を

定義する

from typing import Sequence, TypeVar

T = TypeVar('T') # Declare type variable

def first(l: Sequence[T]) -> T: # Generic function return l[0]

Page 22: Python と型ヒント (Type Hints)

型ヒント :TypeVar● TypeVar のコンストラクタに渡す型変数を表す文字列と代入する変数名は同じでなければいけない

from typing import TypeVar

X = TypeVar('X') # correctZ = TypeVar('Y') # error

mypy

generics_typevar.py:4: error: Unexpected TypeVar() argument value

Page 23: Python と型ヒント (Type Hints)

型ヒント :TypeVar の例● typingモジュールにいくつか定義されてる# Some unconstrained type variables. These are used by the container types.T = TypeVar('T') # Any type. KT = TypeVar('KT') # Key type. VT = TypeVar('VT') # Value type. T_co = TypeVar('T_co', covariant=True) # Any type covariant containers.V_co = TypeVar('V_co', covariant=True) # Any type covariant containers.VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers.T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant.

# A useful type variable with constraints. This represents string types.# TODO: What about bytearray, memoryview?AnyStr = TypeVar('AnyStr', bytes, str)

型の別名

Page 24: Python と型ヒント (Type Hints)

型ヒント : 型別名と直和型● 型別名と直和型の違い

– 型パラメーターとなり得るかfrom typing import Generic, TypeVar, Union AnyStr = TypeVar('AnyStr', str, bytes) class AnyStrNode(Generic[AnyStr]): ... UnionStr = Union[str, bytes] class UnionStrNode(Generic[UnionStr]): ...

mypy

error: Free type variable expected in Generic[...]

Page 25: Python と型ヒント (Type Hints)

型ヒント :ユーザー定義ジェネリック型

● Generic という抽象基底クラスを継承するfrom typing import Generic, TypeVar

T = TypeVar('T')

class Stack(Generic[T]): def __init__(self) -> None: self.items = [] # type: List[T] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop()

s = Stack() # type: Stack[int]s.push(3)s.push(5)s.pop()s.push('x')

s = Stack[int]()...

mypy

mypy

generics_stack.py:19: error: Argument 1 to "push" of "Stack" has incompatible type "str"; expected "int"

error: Generic type not valid as an expression any more (use '# type:' comment instead)

Page 26: Python と型ヒント (Type Hints)

型ヒント : 型消去● ユーザー定義のジェネリック型はインスタンス化できる

from typing import TypeVar, Generic T = TypeVar('T')

class Node(Generic[T]): ...

x = Node[T]() # The type inferred for x is Node[T].print('x.__class__:', x.__class__)y = Node[int]() # The type inferred for y is Node[int].print('y.__class__:', y.__class__)z = Node() # Inferred type is Node[Any].print('z.__class__:', z.__class__)

実行時に型情報は保持していない

$ python generics_type_erasure.py x.__class__: __main__.Node[~T]y.__class__: __main__.Node[~T]z.__class__: __main__.Node[~T]

Page 27: Python と型ヒント (Type Hints)

型ヒント : 数値型階層 (numeric tower)

● numbersモジュールに抽象基底クラスを定義

complex型 int/float 型を許容する

def add_int(num: int) -> int: return num + 1

print(add_int(1))print(add_int(2.4))print(add_int(complex('3+4J')))

# floatは割愛

def add_complex(num: complex) -> complex: return num + complex('1+2J')

print(add_complex(7))print(add_complex(8.14))print(add_complex(complex('9+10J')))

mypy

error: Argument 1 to "add_int" has incompatible type "float"; expected "int"error: Argument 1 to "add_int" has incompatible type "complex"; expected "int"

Page 28: Python と型ヒント (Type Hints)

型ヒント : スタブファイル● 型チェッカーのための拡張モジュール● 型ヒントを Pythonモジュールではなく外部のファイルに書き出すためのもの

● 拡張子は .pyi● 主には ( 型ヒントがない )サードパーティのライブラリと一緒に型ヒントを使うため

– スタブを集める typeshedリポジトリもある● TypeScript でいうところの DefinitelyTyped

Page 29: Python と型ヒント (Type Hints)

型ヒント : スタブの例

class tzinfo: def tzname(self, dt: Optional[datetime]) -> str: ... def utcoffset(self, dt: Optional[datetime]) -> int: ... def dst(self, dt: Optional[datetime]) -> int: ... def fromutc(self, dt: datetime) -> datetime: ..

class timezone(tzinfo): utc = ... # type: tzinfo min = ... # type: tzinfo max = ... # type: tzinfo def __init__(self, offset: timedelta, name: str = '') -> None: ... def __hash__(self) -> int: ...

datetime.pyi

Page 30: Python と型ヒント (Type Hints)

型ヒント : その他● 呼び出し可能オブジェクト● 抽象ジェネリック型● 上界をもつ型変数● 共変性と反変性● 前方参照● キャスト● ...

PEP 484 を読んでください ...

Page 31: Python と型ヒント (Type Hints)

懸念

Page 32: Python と型ヒント (Type Hints)

懸念 :(Python に )必要か?● Python に型ヒントは本当に必要なのか?● Revenge of the Types: 型の復讐 (翻訳)

– Armin Ronacher 氏の問題提起– 欲しいのは強力な型システムそのもの ≠型ヒント

● 代数的データ型 (Algebraic data type) の議論– Python は言語の意味論が壊れてる?

● Python側実装と C 言語側実装による意味論の違い● CPythonインタープリターの最適化のため

Page 33: Python と型ヒント (Type Hints)

懸念 :Typed Clojure の現状● Why we’re no longer using Core.typed● CircleCI が本番環境で 2 年間使っていたが、止

めてしまった話– 型チェックが遅くてインタラクティブに開発でき

ない– core.typed が Clojure 言語全てをカバーしてない– サードパーティライブラリの型アノテーションの

メンテが大変

Page 34: Python と型ヒント (Type Hints)

展望

Page 35: Python と型ヒント (Type Hints)

展望 : 構造的部分型 (Structual Subtyping)

● 構造的部分型の型チェックの前提案– プロトコルという概念を導入

● Sized, Iterable, Iterator など– クラスの継承関係ではなく、メソッド実装の有無

で型チェックを行う (Go 言語のポリモルフィズム )– うまくいけば 3.6 の PEP に出てくるかも?

● クラスの継承関係で部分型を定義するのを公称的部分型 (Nominal Subtyping) と言う

Page 36: Python と型ヒント (Type Hints)

展望 : 構造的部分型への期待?

● [Python-ideas] Structural type checking for PEP 484

– Cory Benfield (hyper の作者 ) からの返信– 自分は型ヒントに特に関心のない開発者の 1 人だったんだけど、これは型チェックに関して懸念していたことに対応できるものだと思う

Page 37: Python と型ヒント (Type Hints)

まとめ

Page 38: Python と型ヒント (Type Hints)

型ヒントの所感● 型ヒントが成功を収めるかどうかは懐疑的

– 型ヒントが Python3 のキラーアプリというのはまだ楽観的過ぎると私は思う→プロパガンダ

● みんな強力な型システムを求めている– NullPointerException の発生しない世界

● 型システムとプログラミングの関係を学ぶ– プログラミングの幅が広がり楽しくなる!

Page 39: Python と型ヒント (Type Hints)

Happy CodingWith Type Hints