SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Pythonの新機能を知ろう!

【Python 3.12への道のり】3.11で導入された可変長ジェネリクス・データクラス変換とは?型関連の機能を紹介

Pythonの新機能を知ろう! 第2回

TypedDictのおさらいとRequired/NotRequired

 型付きの辞書TypedDicについて、Python 3.11では辞書の要素ごとに必須・省略可能が指定できるようになりました。

TypedDictとは?

 必須と省略可の意義を紹介する前に、TypedDictについておさらいしておきましょう。TypedDictは、Python 3.8で導入された、辞書のキーと値の型を厳密に指定するためのクラスです。TypedDictを継承した辞書では、以下のリストのようにキーnameの値はstr、キーpriceとキーquantityの値はintというように型を明確にできます。

リスト typeddict.py
from typing import TypedDict

class Product(TypedDict):		(1)
    name: str
    price: int
    quantity: int

product: Product = {			(2)
    'name': 'MacBook',
    'price': 200000,
    'quantity': 10,
}

product2: Product = {			(3)
    'name': 'MacBook',
    'price': '200000',  # mypyでIncompatible typesエラーとなる
    'quantity': 10,
}

 (1)でTypedDictのクラスProductを定義しており、(2)はその正しい利用例です。(3)ではキーpriceの値がintであるべきところがstrになっており、これはmypyではIncompatible typesエラーとなります。

 TypedDictには、既定ではクラスに列挙された全ての要素が必須という制約があり、一部でも省略すると型チェックでエラーになりますが、クラスの引数にtotal=Falseを与えることで一部が省略可能な辞書とすることはできます。以下のリストはその例です。

リスト typeddict.py
# mypyでMissing keyエラーとなる
product3: Product = {			(1)
    'name': 'MacBook',
    'price': 200000,
}

class ProductPart(TypedDict, total=False):	(2)
    name: str
    price: int
    quantity: int

product_part: ProductPart = {		(3)
    'name': 'MacBook',
    'price': 200000,
}

 (1)ではquantityキーがないのでmypyではMissing keyエラーとなります。(2)のようにtotal=Falseを引数に与えたProductPartクラスでは、(3)のようにquantityキーが省略されたとしてもエラーとはなりません。ただし、nameキーが欠けてもpriceキーが欠けても良いというのがtotal=Falseの動作となるので、意図した省略なのかは静的型チェックでは分かりません。

RequiredとNotRequiredで必須か省略可能かを指定可能

 このように、要素の全てが必須(total=True)、あるいはどれかあるいは全部が欠けていてもよい(total=False)というように極端で、省略されてもそれが意図的なものなのか分からないため、Pythin 3.11では辞書の要素ごとに必須、省略可能を指定できるようになりました。それが、typing.Requiredとtyping.NotRequiredです。Required型修飾子が付いたフィールドは必須となり省略できず、NotRequired型修飾子が付いたフィールドは省略可能になります。以下のリストでは、quantityのみが省略可能になります。

リスト typeddict_rev.py
from typing import TypedDict, Required, NotRequired

class Product(TypedDict):	(1)
    name: Required[str]
    price: Required[int]
    quantity: NotRequired[int]  # 省略可能
…略…
# エラーにならない
product2: Product = {		(2)
    'name': 'MacBook',
    'price': 200000,
}

 (1)は、RequiredとNotRequiredをフィールドに指定したクラスの定義です。なお、各フィールドの必須の有無の既定はtotal引数の指定に準ずるので、total=True(既定)なら全フィールド必須、total=Falseなら全フィールド省略可能になります。

 (2)ではquantityキーを省略していますが、NotRequiredが指定されているのでエラーとはなりません。意味的にも明確になります。

TypedDictのジェネリクス

 TypedDictに関連して、TypedDictのジェネリクス対応についても紹介しておきます。指定できる型は1個ですが、外部から要素の型を指定できるTypedDictが利用できます。以下のリストは、type要素を型引数Tで指定できることで、対応する値をstrでもintでも表現できるようにした例です。

リスト typeddict_generic.py
from typing import TypeVar, Generic, TypedDict

T = TypeVar("T")

class Product(TypedDict, Generic[T]):		(1)
    name: str
    type: T

product_str: Product[str] = {			(2)
    'name': 'MacBook',
    'type': 'PC',
}

product_int: Product[int] = {			(3)
    'name': 'MacBook',
    'type': 101,
}

 (1)が、ジェネリックを用いたTypedDictの定義です。従来、TypedDictと非TypedDictなクラスを同時に継承することは許可されていませんでした。しかし、Python 3.11ではこの制限が解消されたことで、ジェネリクスが実現されました。

 (2)と(3)は、それぞれ型引数をstr、intとしたインスタンス生成の例です。確かに、異なる型の要素を使い分けられることが確認できます。

LiteralString型により関数引数などに文字列リテラルのみの指定が可能に

 型ヒントの一つに、特定の文字列定数のみが有効というLiteralがあります。例えば、文字列リテラル"HTTP"のみを受け入れる場所にはLiteral("HTTP")と型ヒントを与えることで、それ以外の文字列定数は使えなくなります。値が決まっていれば有用な型ヒントになりますが、文字列定数である必要はあるけど中身は何でも良い、という場合には利用できません。

 そこでPython 3.11ではLiteralString型が導入されて、任意の文字列定数を受入れ可能なことを明示できるようになりました。これは例えば関数がSQL文を引数として受け入れる場合に、それが定数であることを保証する際などに有用です。外部でSQL文が生成されて渡されるとすると、SQLインジェクションなどのセキュリティリスクを生じる可能性があるからです。このようなときに定数であることを保証すれば、危険なSQL文が渡されるリスクを低減できます。以下のリストは、これを単純な形で表してみた例です。

リスト literalstring.py
from typing import LiteralString

# 検索語句を必ず文字列リテラルで受け取る関数
def db_access(s: LiteralString) -> bool:	(1)
    print(f"Execute: SELECT * FROM table WHERE name = '{s}'")
    return True

lstr: LiteralString = 'nao'
astr: str = input()		# たとえば nao' OR '1' = '1

db_access(lstr)			(2)
db_access(astr)			# Pylanceでエラー

 (1)は、検索語句をLiteralString型の引数で受け取り、SQLのSELECT文を発行するということを想定した関数の定義です。実際にはSQL文を表示しているだけです。

 (2)以降は、その呼び出し例です。LiteralStringであるlstrを渡す呼び出しには問題ないですが、strであるastrを渡している方はPylanceで「"astr" は "LiteralString" と互換性がありません」のエラーとなります(mypyではサポートされないようでスルーされます)。

 このように、引数をLiteralStringに制限することで、外部入力を渡してしまうといったコードを実行前に検出することができます。

次のページ
dataclassのおさらいとデータクラス変換の活用

この記事は参考になりましたか?

Pythonの新機能を知ろう!連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/18532 2023/10/31 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー