コンテンツにスキップ

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_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でしたか?それともuppercasefirst_uppercasecapitalize

次に、古いプログラマーの友人であるエディタのオートコンプリートを試します。

関数の最初のパラメータである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

これで、それを修正し、agestr(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 は、3つの項目、int、別の int、および str を持つ 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」という単語は、値がオプションであることを意味するように思われるため、Optional の代わりに Union をお勧めします。実際には、必須でなくても「None になる可能性がある」ことを意味します。

Union[SomeType, None] の方が意味をより明示的であると思います。

これは単に単語と名前に関するものです。しかし、これらの単語は、あなたとあなたのチームメイトがコードについてどのように考えるかに影響を与える可能性があります。

例として、この関数を見てみましょう。

from typing import Optional


def say_hi(name: Optional[str]):
    print(f"Hey {name}!")

パラメータ nameOptional[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}!")

そうすれば、OptionalUnion のような名前を気にする必要もなくなります。😎

ジェネリック型

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

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

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

情報

すでにチュートリアルをすべて見て、型についてもっと知りたくなった場合は、mypyの「チートシート」が良い参考資料です。