コンテンツへスキップ

Pythonの型の紹介

Pythonにはオプションの「型ヒント」(「型アノテーション」とも呼ばれる)のサポートがあります。

これらの「型ヒント」またはアノテーションは、変数のを宣言できる特殊な構文です。

変数に型を宣言することで、エディターやツールはより良いサポートを提供できます。

これはPythonの型ヒントに関する簡単なチュートリアル/復習です。FastAPIで使用するために必要な最低限のことがカバーされています...実際にはごくわずかです。

FastAPIはすべてこれらの型ヒントに基づいており、多くの利点とメリットをもたらします。

しかし、FastAPIを一度も使用しなくても、それらについて少し学ぶことで利益を得ることができます。

Note

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_namelast_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

エディターが変数の型を知っているので、補完だけでなく、エラーチェックも得られます。

これで、agestr(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

型パラメーターを持つジェネリック型

dictlistsettupleのように、他の値を含めることができるデータ構造がいくつかあります。そして、内部の値も独自の型を持つことができます。

内部型を持つこれらの型は「ジェネリック」型と呼ばれます。そして、それらを宣言することが可能であり、内部型も指定できます。

これらの型と内部型を宣言するには、標準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+」です。

リスト

例えば、変数をstrlistとして定義しましょう。

同じコロン(:)構文で変数を宣言します。

型として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)

情報

角括弧内のこれらの内部型は「型パラメーター」と呼ばれます。

この場合、strList(またはPython 3.9以降ではlist)に渡される型パラメーターです。

これは、「変数itemslistであり、このリスト内の各項目はstrである」ことを意味します。

ヒント

Python 3.9以降を使用している場合、typingからListをインポートする必要はなく、代わりに通常のlist型を使用できます。

そうすることで、リストの項目を処理している間でも、エディターがサポートを提供できます。

型がなければ、それを達成するのはほぼ不可能です。

変数itemはリストitemsの要素の1つであることに注意してください。

それでも、エディターはそれがstrであることを認識し、それに対するサポートを提供します。

タプルとセット

tuplesetを宣言する場合も同様です。

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は、int、別のint、およびstrの3つの項目を持つtupleです。
  • 変数items_ssetであり、その各項目は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)

これは次のことを意味します

  • 変数pricesdictです。
    • この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)

どちらの場合も、これはitemintまたは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}!")

パラメータnameOptional[str]と定義されていますが、オプションではありません。パラメータなしで関数を呼び出すことはできません。

say_hi()  # Oh, no, this throws an error! 😱

nameパラメータはデフォルト値がないため、依然として必須オプションではない)です。それでも、nameNoneを値として受け入れます。

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}!")

そうすれば、OptionalUnionのような名前について心配する必要もありません。😎

ジェネリック型

角括弧内に型パラメーターを取るこれらの型は、ジェネリック型またはジェネリクスと呼ばれます。例えば、

ジェネリクスとして(角括弧と内部の型と共に)同じ組み込み型を使用できます。

  • list
  • tuple
  • set
  • dict

そして、Python 3.8と同様に、typingモジュールから

  • ユニオン
  • Optional (Python 3.8と同じ)
  • ...その他。

Python 3.10では、ジェネリックなUnionOptionalを使用する代わりに、垂直バー(|を使用して型のユニオンを宣言できます。これははるかに優れていてシンプルです。

ジェネリクスとして(角括弧と内部の型と共に)同じ組み込み型を使用できます。

  • list
  • tuple
  • set
  • dict

そして、Python 3.8と同様に、typingモジュールから

  • ユニオン
  • オプション
  • ...その他。
  • リスト
  • タプル
  • セット
  • 辞書
  • ユニオン
  • オプション
  • ...その他。

型としてのクラス

クラスを変数の型として宣言することもできます。

名前を持つ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_personPersonクラスのインスタンスである」ことを意味することに注意してください。

これは「one_personPersonというクラスである」という意味ではありません。

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型を(追加のクラス、デコレーターなどを追加する代わりに)単一の場所で使用することで、FastAPIが多くの作業を代行してくれるということです。

情報

すでにすべてのチュートリアルを終えて、型についてさらに詳しく知るために戻ってきた場合は、mypyの「チートシート」が良いリソースです。