Pythonの型入門¶
Pythonには、オプションの「型ヒント」(「型アノテーション」とも呼ばれる)のサポートがあります。
これらの「型ヒント」またはアノテーションは、変数の型を宣言できる特殊な構文です。
変数に型を宣言することで、エディタやツールはより良いサポートを提供できます。
これは、Pythonの型ヒントに関する簡単なチュートリアル/復習です。FastAPIでそれらを使用するために必要な最低限のことだけを扱っています...実際にはごくわずかです。
FastAPIはすべてこれらの型ヒントに基づいており、それらは多くの利点と利益をもたらします。
しかし、たとえFastAPIを一度も使わなくても、それらについて少し学ぶことで利益を得られるでしょう。
注釈
Pythonの専門家で、型ヒントについてすべて知っている場合は、次の章に進んでください。
動機¶
簡単な例から始めましょう
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
このプログラムを実行すると出力されます
John Doe
この関数は次のことを行います
first_name
とlast_name
を受け取ります。title()
を使用して、それぞれの最初の文字を大文字に変換します。- 連結し、間にスペースを入れます。
def get_full_name(first_name, last_name):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
編集¶
非常にシンプルなプログラムです。
しかし、今、これをゼロから書いていると想像してみてください。
ある時点で関数の定義を始め、パラメータを準備していました...
しかし、その後、「最初の文字を大文字に変換するあのメソッド」を呼び出す必要があります。
upper
だったか? uppercase
だったか? first_uppercase
だったか? capitalize
だったか?
そこで、古いプログラマーの友、エディタのオートコンプリートを試します。
関数の最初のパラメータであるfirst_name
を入力し、ドット(.
)を入力して、Ctrl+Space
を押して補完をトリガーします。
しかし、残念ながら、何も役立つものは得られません
型を追加する¶
前のバージョンから1行だけ変更します。
関数のパラメータであるこの部分を、正確に次のように変更します。
first_name, last_name
から
first_name: str, last_name: str
それだけです。
これらが「型ヒント」です。
def get_full_name(first_name: str, last_name: str):
full_name = first_name.title() + " " + last_name.title()
return full_name
print(get_full_name("john", "doe"))
これは、次のようにデフォルト値を宣言することとは異なります。
first_name="john", last_name="doe"
それは別のことです。
コロン(:
)を使用しており、イコール(=
)ではありません。
そして、型ヒントを追加しても、通常、型ヒントなしの場合と何が変わるわけではありません。
しかし今、その関数を作成している途中で、型ヒントを使用していると想像してください。
同じ時点で、Ctrl+Space
でオートコンプリートをトリガーしようとすると、次のように表示されます。
これで、オプションをスクロールして、「ピンとくる」ものを見つけることができます。
さらなる動機¶
この関数を確認してください。すでに型ヒントがあります。
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + age
return name_with_age
エディタが変数の型を知っているので、補完だけでなくエラーチェックも受けられます。
これで、age
をstr(age)
で文字列に変換して修正する必要があることがわかります。
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
型の宣言¶
型ヒントを宣言する主な場所を見ました。それは関数のパラメータとしてです。
これは、FastAPIでそれらを使用する主な場所でもあります。
単純な型¶
str
だけでなく、すべての標準的なPython型を宣言できます。
たとえば、次のように使用できます。
int
float
bool
bytes
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
return item_a, item_b, item_c, item_d, item_d, item_e
型パラメータを持つジェネリック型¶
dict
、list
、set
、tuple
のように、他の値を含むことができるデータ構造があります。そして、内部の値も独自の型を持つことができます。
内部の型を持つこれらの型は「ジェネリック」型と呼ばれます。そして、それらを内部の型を含めて宣言することが可能です。
これらの型と内部の型を宣言するには、標準のPythonモジュールtyping
を使用できます。これは、これらの型ヒントをサポートするために特別に存在します。
新しいバージョンのPython¶
typing
を使用する構文は、Python 3.6から最新バージョン(Python 3.9、Python 3.10などを含む)まですべてのバージョンと互換性があります。
Pythonの進化に伴い、新しいバージョンではこれらの型アノテーションのサポートが向上しており、多くの場合、型アノテーションを宣言するためにtyping
モジュールをインポートして使用する必要すらありません。
プロジェクトでより新しいバージョンのPythonを選択できる場合は、その追加のシンプルさを活用できます。
すべてのドキュメントには、Pythonの各バージョンと互換性のある例が含まれています(違いがある場合)。
たとえば、「Python 3.6+」はPython 3.6以降(3.7、3.8、3.9、3.10などを含む)と互換性があることを意味します。「Python 3.9+」はPython 3.9以降(3.10などを含む)と互換性があることを意味します。
最新バージョンのPythonを使用できる場合は、最新バージョンの例を使用してください。それらは最も優れたシンプルな構文を持っています。たとえば、「Python 3.10+」です。
リスト¶
たとえば、str
のlist
である変数を定義してみましょう。
コロン (:
) 構文と同じように変数を宣言します。
型としてlist
を置きます。
リストはいくつかの内部型を含む型なので、角括弧の中にそれらを置きます。
def process_items(items: list[str]):
for item in items:
print(item)
typing
からList
(大文字のL
)をインポートします。
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
コロン (:
) 構文と同じように変数を宣言します。
型として、typing
からインポートしたList
を置きます。
リストはいくつかの内部型を含む型なので、角括弧の中にそれらを置きます。
from typing import List
def process_items(items: List[str]):
for item in items:
print(item)
情報
角括弧内のこれらの内部型は「型パラメータ」と呼ばれます。
この場合、str
はList
(またはPython 3.9以降ではlist
)に渡される型パラメータです。
これは、「変数items
はlist
であり、このリスト内の各項目はstr
である」という意味です。
ヒント
Python 3.9以降を使用している場合、typing
からList
をインポートする必要はなく、代わりに通常のlist
型を使用できます。
そうすることで、エディターはリストの項目を処理している間でもサポートを提供できます。
型がない場合、これはほとんど不可能です。
変数item
はリストitems
の要素の1つであることに注意してください。
それでも、エディターはそれがstr
であることを認識し、それに対するサポートを提供します。
タプルとセット¶
tuple
とset
を宣言する場合も同様です。
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
return items_t, items_s
from typing import Set, Tuple
def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
return items_t, items_s
これは以下を意味します
- 変数
items_t
は3つの項目を持つtuple
で、int
、別のint
、およびstr
です。 - 変数
items_s
はset
で、その各項目はbytes
型です。
辞書¶
dict
を定義するには、カンマで区切られた2つの型パラメータを渡します。
最初の型パラメータはdict
のキー用です。
2番目の型パラメータはdict
の値用です。
def process_items(prices: dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
from typing import Dict
def process_items(prices: Dict[str, float]):
for item_name, item_price in prices.items():
print(item_name)
print(item_price)
これは以下を意味します
- 変数
prices
はdict
です。- この
dict
のキーはstr
型です(各項目の名前としましょう)。 - この
dict
の値はfloat
型です(各項目の価格としましょう)。
- この
Union¶
変数がいくつかの型のいずれかであることを宣言できます。たとえば、int
またはstr
。
Python 3.6以降(Python 3.10を含む)では、typing
からUnion
型を使用して、角括弧内に受け入れ可能な型を入れることができます。
Python 3.10には、可能な型を縦棒 (|
)で区切って記述できる新しい構文もあります。
def process_item(item: int | str):
print(item)
from typing import Union
def process_item(item: Union[int, str]):
print(item)
どちらの場合も、これはitem
がint
またはstr
のいずれかであることを意味します。
None
の可能性¶
値がstr
のような型を持つ可能性があるが、None
である可能性もあることを宣言できます。
Python 3.6以降(Python 3.10を含む)では、typing
モジュールからOptional
をインポートして使用することで宣言できます。
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
単にstr
とする代わりにOptional[str]
を使用すると、値が常にstr
であると仮定しているが、実際にはNone
である可能性もある場合に、エディターがエラーを検出するのに役立ちます。
Optional[Something]
は実際にはUnion[Something, None]
のショートカットであり、同等です。
これは、Python 3.10ではSomething | None
を使用できることも意味します。
def say_hi(name: str | None = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Optional
def say_hi(name: Optional[str] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
from typing import Union
def say_hi(name: Union[str, None] = None):
if name is not None:
print(f"Hey {name}!")
else:
print("Hello World")
Union
またはOptional
の使用¶
Python 3.10未満のバージョンを使用している場合、私の非常に主観的な視点からのヒントです。
- 🚨
Optional[SomeType]
の使用は避けてください。 - 代わりに✨
Union[SomeType, None]
を使用してください ✨。
どちらも同等で、内部的には同じですが、私はOptional
ではなくUnion
を推奨します。なぜなら、「optional」という言葉は値がオプションであることを示唆するように思えるかもしれませんが、実際には「None
になる可能性がある」という意味であり、それがオプションではなく、依然として必須である場合でもそうです。
Union[SomeType, None]
の方が、その意味についてより明確だと思います。
それは単に単語と名前に関するものです。しかし、これらの単語は、あなたやあなたのチームメイトがコードについて考える方法に影響を与える可能性があります。
例として、この関数を見てみましょう。
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
🤓 その他のバージョンとバリアント
def say_hi(name: str | None):
print(f"Hey {name}!")
パラメータname
はOptional[str]
として定義されていますが、オプションではありません。パラメータなしで関数を呼び出すことはできません。
say_hi() # Oh, no, this throws an error! 😱
name
パラメータはデフォルト値がないため、依然として必須です(オプションではありません)。それでも、name
はNone
を値として受け入れます。
say_hi(name=None) # This works, None is valid 🎉
良いニュースは、Python 3.10以降では、型結合を定義するために|
を使用するだけで済むため、この心配をする必要がなくなることです。
def say_hi(name: str | None):
print(f"Hey {name}!")
🤓 その他のバージョンとバリアント
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
そうすれば、Optional
やUnion
のような名前について心配する必要はありません。😎
ジェネリック型¶
角括弧で型パラメータを取るこれらの型は、ジェネリック型またはジェネリクスと呼ばれます。たとえば、
同じ組み込み型をジェネリックとして使用できます(角括弧と型を内部に記述)。
リスト
タプル
セット
辞書
そして、Python 3.8と同様に、typing
モジュールから
Union
Optional
(Python 3.8と同じ)- ...など。
Python 3.10では、ジェネリックのUnion
とOptional
を使用する代わりに、縦棒 (|
)を使用して型のユニオンを宣言できます。これははるかに優れていてシンプルです。
同じ組み込み型をジェネリックとして使用できます(角括弧と型を内部に記述)。
リスト
タプル
セット
辞書
そして、Python 3.8と同様に、typing
モジュールから
Union
Optional
- ...など。
リスト
タプル
セット
辞書
Union
Optional
- ...など。
型としてのクラス¶
クラスを変数の型として宣言することもできます。
名前を持つPerson
クラスがあるとしましょう。
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
次に、変数をPerson
型として宣言できます。
class Person:
def __init__(self, name: str):
self.name = name
def get_person_name(one_person: Person):
return one_person.name
そして、再び、すべてのエディタサポートを受けられます。
これは「one_person
がPerson
クラスのインスタンスである」ことを意味することに注意してください。
「one_person
がPerson
というクラスである」という意味ではありません。
Pydanticモデル¶
Pydanticはデータ検証を実行するためのPythonライブラリです。
データの「形状」を、属性を持つクラスとして宣言します。
そして、各属性は型を持っています。
次に、そのクラスのインスタンスをいくつかの値で作成すると、値が検証され、適切な型に変換され(該当する場合)、すべてのデータを含むオブジェクトが返されます。
そして、その結果のオブジェクトですべてのエディタサポートを受けられます。
Pydantic公式ドキュメントからの例
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: datetime | None = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: list[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
from datetime import datetime
from typing import List, Union
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = "John Doe"
signup_ts: Union[datetime, None] = None
friends: List[int] = []
external_data = {
"id": "123",
"signup_ts": "2017-06-01 12:22",
"friends": [1, "2", b"3"],
}
user = User(**external_data)
print(user)
# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3]
print(user.id)
# > 123
FastAPIはPydanticに基づいて構築されています。
これらすべての詳細については、チュートリアル - ユーザーガイドで実践的に説明されています。
ヒント
Pydanticは、デフォルト値なしでOptional
またはUnion[Something, None]
を使用すると特殊な動作をします。これについては、Pydanticの必須オプションフィールドに関するドキュメントで詳しく読むことができます。
メタデータ注釈付き型ヒント¶
Pythonには、Annotated
を使用してこれらの型ヒントに追加のメタデータを配置できる機能もあります。
Python 3.9では、Annotated
は標準ライブラリの一部なので、typing
からインポートできます。
from typing import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
Python 3.9未満のバージョンでは、typing_extensions
からAnnotated
をインポートします。
FastAPIと共にすでにインストールされています。
from typing_extensions import Annotated
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
return f"Hello {name}"
Python自体は、このAnnotated
で何も行いません。エディタやその他のツールにとって、型は依然としてstr
です。
しかし、このAnnotated
のスペースを使用して、アプリケーションの動作方法に関する追加のメタデータをFastAPIに提供できます。
覚えておくべき重要なことは、Annotated
に渡す最初の型パラメータが実際の型であるということです。残りは、他のツール用のメタデータにすぎません。
今のところ、Annotated
が存在し、それが標準的なPythonであることを知っていれば十分です。😎
後で、それがどれほど強力であるかを確認できます。
ヒント
これが標準Pythonであるという事実は、コードの分析やリファクタリングに使用するツールなど、エディタで可能な限り最高の開発者体験を得られることを意味します。✨
また、あなたのコードが他の多くのPythonツールやライブラリと高い互換性を持つことを意味します。🚀
FastAPIにおける型ヒント¶
FastAPIは、これらの型ヒントを利用していくつかのことを行います。
FastAPIで型ヒントを使用してパラメータを宣言すると、以下が得られます。
- エディターのサポート.
- 型チェック.
...そしてFastAPIは同じ宣言を使用して次を行います。
- 要件の定義: リクエストパスパラメータ、クエリパラメータ、ヘッダー、ボディ、依存関係などから。
- データの変換: リクエストから必要な型へ。
- データの検証: 各リクエストから送られてくるデータを検証します。
- データが無効な場合にクライアントに返される自動エラーを生成します。
- OpenAPIを使用してAPIをドキュメント化します。
- これは、自動的な対話型ドキュメントユーザーインターフェイスで使用されます。
これらすべては抽象的に聞こえるかもしれません。心配しないでください。チュートリアル - ユーザーガイドでこれらすべてを実際に確認できます。
重要なのは、標準的なPythonの型を1箇所で使用するだけで(追加のクラスやデコレーターなどを追加する代わりに)、FastAPIが多くの作業を行ってくれるということです。
情報
すでにすべてのチュートリアルを終えて型についてさらに学習するために戻ってきた場合は、mypy
の「チートシート」が良い参考資料になります。