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
この関数を確認してください。すでに型ヒントがあります
エディターが変数の型を認識しているため、補完だけでなく、エラーチェックも受けられます
def get_name_with_age(name: str, age: int):
name_with_age = name + " is this old: " + str(age)
return name_with_age
これで、それを修正し、age
をstr(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つの項目、int
、別のint
、およびstr
を持つtuple
です。 - 変数
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
型です(例えば、各項目の価格)。
- この
ユニオン¶
変数に、例えば、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」という単語は、値がオプションであることを意味するように思われるため、Optional
の代わりに Union
をお勧めします。実際には、必須でなくても「None
になる可能性がある」ことを意味します。
Union[SomeType, None]
の方が意味をより明示的であると思います。
これは単に単語と名前に関するものです。しかし、これらの単語は、あなたとあなたのチームメイトがコードについてどのように考えるかに影響を与える可能性があります。
例として、この関数を見てみましょう。
from typing import Optional
def say_hi(name: Optional[str]):
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}!")
そうすれば、Optional
や Union
のような名前を気にする必要もなくなります。😎
ジェネリック型¶
角括弧内に型パラメータを取るこれらの型は、ジェネリック型またはジェネリクスと呼ばれます。例を挙げると、
組み込み型をジェネリック型として(角括弧と内部の型を使用して)使用できます。
list
tuple
set
dict
また、Python 3.8 と同様に、typing
モジュールから
ユニオン
Optional
(Python 3.8 と同じ)- ...など。
Python 3.10 では、ジェネリック型 Union
および Optional
を使用する代わりに、型のユニオンを宣言するために 縦棒 (|
) を使用できます。これははるかに優れており、よりシンプルです。
組み込み型をジェネリック型として(角括弧と内部の型を使用して)使用できます。
list
tuple
set
dict
また、Python 3.8 と同様に、typing
モジュールから
ユニオン
Optional
- ...など。
リスト
Tuple
Set
辞書
ユニオン
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
の「チートシート」が良い参考資料です。