目次
7.3 さまざまなタイプの NewType を作成するために使用されます
1. 説明
明確で信頼性の高いコードを容易にするために Python が提供する強力なツールの 1 つは、「型ヒント」の概念です。「 Python は動的型付け言語であるのに、なぜわざわざ型を気にする必要があるの?」と疑問に思われるかもしれません。
コーディングのベスト プラクティスに興味のあるデータ エンジニアまたは Python 初心者にとって、Python コードのタイプ ヒントを理解して適用することは、大きな資産となります。
この記事では、型ヒント、その用途、Python プログラミングにおける利点について詳しく見ていきます。Dagster は型アノテーション フレームワークであるため 、データ エンジニアリング パイプラインで型を使用して読みやすさを向上させ、エラーを発生しにくくする方法についても説明します。これは、将来の自分や、コードを操作する可能性のある他の開発者にマップを提供するようなものです。これは、関数やクラスに出入りするデータ型の詳細を示すマップです。
2. ダイナミック型とは何ですか?
Python は動的型付け言語です。Java や C++ などの静的に型指定された言語では、変数を使用する前にその型を宣言する必要があります。たとえば、変数が整数、浮動小数点、文字列などであるかどうかを指定する必要があります。Python では、実行前にデータ型を考慮せずにコードを作成できます。これは、Python が特に初心者に適している機能の 1 つです。
たとえば、変数を宣言し、その型を指定せずに値を直接割り当てることができるため、「動的型付け」という名前が付けられています。Python インタープリターは、実行時に値とその型を暗黙的にバインドします。
x = 10 # x is an integer
x = "Hello" # now x is a string
最初の行の は整数です。2行目では同じものが文字列になります。動的型付けの性質により、Python はこの変換をシームレスに処理できます。x
x
ただし、この動的な性質により、特にデータ フローがすぐに明らかではない大規模なコード ベースや複雑なデータ処理パイプラインでは、デバッグが困難なバグが発生する可能性があります。
標準ライブラリの一部として PEP 484 を介して Python 3.5 に導入された型ヒントを使用すると、変数、関数の引数、または戻り値の予期される型を指定できます。
2.1 なぜタイプヒントを使用するのですか?
動的型付けにより柔軟性が得られる一方で、潜在的なエラーが発生する余地も生じます。ここで型ヒントが役に立ちます。これらにより、コードの可読性が大幅に向上し、型関連のエラーを防ぐことができます。
コードの可読性の向上:型ヒントは、関数が期待する引数の型と関数が返すものを開発者が理解するのに役立つドキュメントの形式として機能します。この強化された明確さにより、コードがより読みやすく、理解しやすくなります。
エラー検出:「pyright」などのツールを使用して、Python コードを静的に分析できます。型ヒントに基づいてコード内の型の一貫性をチェックし、実行前に型関連のエラーを警告します。Dagster チームが mypy を完全にスキップして pypy だけを使用することを推奨している理由をご覧ください。mypy
IDE サポートの向上:多くの統合開発環境 (IDE) とリンターは、タイプ ヒントを利用して、より優れたコード補完、エラー チェック、およびリファクタリングを提供できます。
大規模プロジェクトの促進: 複数の開発者がいる大規模プロジェクトの場合、タイプ ヒントはデータ構造とコードベース全体のフローを理解するのに非常に役立ちます。パブリック Python プロジェクトに型アノテーションを含めて維持する方法に関するガイダンスを公開しました。
2.2 制限事項
実行時に強制されない: Python の型ヒントは強制されず、ヒントのみであり、指定された型が実際の値と一致しない場合でも、Python インタープリターはエラーを生成しません。このため、型ヒントでは型安全性を強制できるのに、型安全性は強制できないという誤解が生じる可能性があります。
過度に複雑: 小規模または単純なスクリプトの場合、型ヒントは過剰に見える可能性があり、単純であるべきコードが複雑になる可能性があります。
柔軟性の低さ: Python の人気の理由の 1 つはその動的な性質であり、型ヒントによってこれが制限される可能性があります。
3. 基本タイプのヒント
Python のモジュールには、Python コードの型ヒントを提供するいくつかの関数とクラスが含まれています。さまざまなシナリオでタイプヒントを適用する方法を次に示します。typing
3.1 変数の型を宣言する
変数の型ヒントを提供するには、コロン記号の後に型を使用します。以下に例を示します。:
age: int = 20
name: str = "Alice"
is_active: bool = True
ここでのプロンプトは、整数、文字列、およびブール値です。age
name
is_active
3.2 関数のアノテーション
関数のパラメータと戻り値に型ヒントを提供できます。これは、他の開発者が関数に必要な引数の型と関数が返す型を理解するのに役立ちます。
def greet(name: str) -> str: \ return f"Hello, {name}
」
この例では、関数は文字列を予期しており、文字列を返します。
4. Python の組み込み型
Python にはいくつかの組み込み型があります。最も一般的に使用されるのは次のとおりです。
int
: 整数を表しますfloat
: 浮動小数点数を表しますbool
: ブール値 (true または false) を表します。str
: 文字列を表します
リスト、タプル、辞書などの複雑な型もあり、これらを使用してより詳細な型ヒントを提供できます。これについては後で説明します。
Python の主なタイプのリストも付録にあり ます。
4.1 アトミック型と複合型
Python では、型ヒントの点でアトミック型と複合型が区別されます。アトミック型 ( 、 、 など) は単純で分割不可能であり、その型アノテーションは型自体を使用して直接提供できます。int
float
str
str
def my_function(my_string: str) -> int:
return len(my_string)
一方、複合型は他の型と同様であり、他の型で構成されており、Python 3.9 より前では、整数のリストなど、型モジュールから特定の定義をインポートする必要がよくありました。List
Dict
typing.List[int]
from typing import List
def my_function(numbers: List[int]) -> int:
return sum(numbers)
Python の新しいバージョンでは、 の代わりに と書くことができます。list[str]
typing.List[int]
5. 関数のコメント
型ヒントは、関数シグネチャに組み込むと特に便利です。これにより、開発者は関数に必要なパラメータの種類を理解できるだけでなく、関数が何を返すかを理解することもできます。
5.1 関数のパラメータの型と戻り値の型を指定する方法
パラメーターのシンボルと戻り値の型のシンボルを使用して、パラメーターの型と関数の戻り値の型を指定できます。一般的な構文は次のとおりです。:
->
def function_name(arg1: type1, arg2: type2, ...) -> return_type:
# function body
この構文では、 、 などは関数パラメータであり、 などはこれらのパラメータの型です。関数によって返される値の型です。arg1
arg2
type1
type2
return_type
5.2 関数シグネチャでの型ヒントの使用例
長方形の面積を計算する関数を考えてみましょう。
def area_rectangle(length: float, breadth: float) -> float:
return length * breadth
この関数では浮動小数点数が期待されており、この関数は浮動小数点数も返します。この関数は、整数を渡しても、あるいは浮動小数点数に変換できる文字列を渡しても機能しますが、型ヒントを見ると、この関数が浮動小数点数を処理するように設計されていることがわかります。length
breadth
別の例としては、整数のリストを受け取り、その合計を整数として返す関数が挙げられます。
def sum_elements(numbers: list[int]) -> int:
return sum(numbers)
この例では、パラメーターのプロンプトは整数のリストであり、戻り値の型は整数です。numbers
これらの型ヒントは、実行時に型チェックを強制しないことに注意してください。これらは、実際の型が指定された型と一致しない場合、Python は を発生させないことを開発者に思い出させるものです。TypeError
6. 複合型
Python の type モジュールには、より複雑な型ヒントを提供するために使用できるいくつかのクラスが用意されています。最も一般的に使用されるクラスのいくつかを次に示します。
6.1 リスト、辞書、タプル、セット
、、、およびクラスを使用して、それぞれリスト、辞書、タプル、セットの型ヒントを提供できます。これらをパラメータ化して、より詳細な型ヒントを提供することができます。list
dict
tuple
set
# A list of integers
numbers: list[int] = [1, 2, 3]
# A dictionary with string keys and float values
weights: dict[str, float] = {"apple": 0.182, "banana": 0.120}
# A tuple with an integer and a string
student: tuple[int, str] = (1, "John", "True")
# A set of strings
flags: set[str] = {"apple", "banana", "cherry"}
これらの例では、ヒントは整数のリスト、文字列キーと浮動小数点値を含む辞書、整数と文字列を含むタプル、および文字列のセットです。numbers
weights
student
flags
6.2 オプション
型ヒントを使用して、変数が特定の型であることを示すことができます。Optional
None
from typing import Optional
def find_student(student_id: int) -> Optional[dict[str, str]]:
# If the student is found, return a dictionary containing their data
# If the student is not found, return None
6.3 結合
型ヒントは、変数が複数の型のいずれかであることを示すために使用されます。たとえば、変数が または の場合、次のように型ヒントを提供できます。Union[
str,
int]
from typing import Union
def process(data: Union[str, int]) -> None:
# This function can handle either a string or an integer
Python の新しいバージョンでは、パイプ (|) 演算子を使用して、いくつかのオプションの 1 つである型を示すことができるため、次の必要がなくなります。Union
def process(data: str | int) -> None:
# This function can handle either a string or an integer
6.4 任意
このクラスは、変数が任意の型であることを示すために使用されます。これは、型ヒントをまったく提供しないことと同じです。Any
from typing import Any
def process(data: Any) -> None:
# This function can handle data of any type
モジュール内のこれらのツールは、詳細な型ヒントを提供するのに役立ち、コードの理解とデバッグが容易になります。typing
ただし、Python の型ヒントはオプションであり、実行時に強制されないことに注意してください。これらは、タイプ セーフを強制する方法ではなく、開発者用のツールとして意図されています。
7. ユーザー定義型
Python では、クラスを使用して独自の型を定義できます。これがカスタム型を作成するための基本メカニズムです。これらのクラスは、組み込み型と同様に型ヒントで使用できます。このモジュールには、 や など、より具体的なタイプを作成するための追加ツールも提供されます。typing
Type
NewType
7.1 クラスを使用して独自の型を定義する
クラスを作成し、それを型ヒントとして使用できます。以下に例を示します。
class Student:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def print_student_details(student: Student) -> None:
print(student.name, student.age)
Student
関数内の型ヒントとして使用されるユーザー定義型です。print_student_details
7.2 型ヒントについてType
モジュール内のクラスを使用すると、変数がクラスのインスタンスではなくクラスになることを示すことができます。これは通常、ファクトリ関数など、関数パラメータがクラスであると予想される場合に使用されます。Type
typing
<span style="color:#3a3631"><span style="background-color:#faf9f7"><span style="background-color:#3a3631"><span style="color:#dad8d6"><code>from typing import Type
def create_student(cls: Type[Student], name: str, age: int) -> Student:
return cls(name, age)
</code></span></span></span></span>
この例では、最初の引数としてクラス (またはそのサブクラス) が必要です。create_student
Student
Student
7.3 異なるタイプを作成する場合NewType
NewType
さまざまなタイプを作成するために使用されます。それ以外は同じである 2 つのタイプを区別したい場合に便利です。
たとえば、プログラムで学生 ID とコース ID を処理していて、それらを混同しないようにしたいとします。どちらも整数として表されるため、次を使用して 2 つの異なる型を作成できます。NewType
from typing import NewType
StudentID = NewType('StudentID', int)
CourseID = NewType('CourseID', int)
def get_student(student_id: StudentID) -> None:
# Fetch student data...
def enroll_in_course(student_id: StudentID, course_id: CourseID) -> None:
# Enroll the student in the course...
と は両方とも整数ですが、これらは異なる型とみなされ、同じ意味で使用することはできません。ただし、このチェックは実行時ではなく、 のようなものを使用した静的型チェック中に強制されることに注意してください。StudentID
CourseID
mypy
8. ジェネリック医薬品
ジェネリックを使用すると、さまざまな型に適用する関数、クラス、またはデータ構造を定義できます。モジュール内のクラスと関数は、ジェネリック型を定義するために使用されます。たとえば、リストにはあらゆるタイプの要素を含めることができるため、ユニバーサル データ構造です。Generic
TypeVar
typing
8.1 型変数
TypeVar は、任意の型の型変数を定義するために使用されます。特定の型はクライアント コードによって決定されます。以下に例を示します。
from typing import TypeVar
T = TypeVar('T')
def first_element(lst: List[T]) -> T:
return lst[0]
ここで、 は型変数であり、任意の型を使用できます。この関数は任意の型のリストを処理し、その型の要素を返します。の特定のタイプは、関数に渡されるリストによって決まります。T
first_element
T
8.2 一般
Generic
ジェネリック クラスを定義するために使用されます。ジェネリック クラスは、クラス内の型ヒントで使用される複数の型で初期化できます。
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, value: T):
self.value = value
def get(self) -> T:
return self.value
以下は、どの型でも機能するジェネリック クラスです。インスタンスを作成するときに、プロパティとメソッドで使用される型を指定できます。Box
T
Box
T
value
get
box1 = Box[int](10)
box2 = Box[str]("Hello")
box1
は整数を含む、 は文字列を含む。Box
box2
Box
9. タイプチェックpyright
このような型チェッカーは、Python で型ヒントを強制するために使用されるツールです。Dagster では、 pyrightが他の代替手段よりも速いため、非常に気に入っています。pyright
mypy
Python 自体は動的に型指定される言語です。つまり、型チェックは実行時に行われ、型ヒント ルールは強制されません。指定されたデータ型でサポートされていない操作を実行しようとすると、Python は実行時にエラーを生成します。たとえば、オブジェクトに対して未定義のメソッドを呼び出すと、実行時にエラーが発生するだけです。
ただし、大規模または複雑なシステムを開発する場合、型の一貫性を強制すると、潜在的なバグを早期に発見するのに役立ちます。静的型チェックを実行します。つまり、実際にコードを実行する前に変数、関数パラメータ、戻り値の型をチェックします。これは、コード内で提供される型ヒントを使用して行われます。コードを実行したり実行したりするのではなく、コードを読み取って分析するだけであることを理解することが重要です。pyright
pyright
9.1 型チェッカーを使用して型を確認する方法
使用するには、まずインストールする必要があります。pyright
pip install pyright
次に、Python ファイルを調べるために、ファイルを引数として実行します。pyright
pyright my_file.py
その後、Pyright はファイルを分析し、見つかった種類のエラーを報告します。
たとえば、 をパラメータとして受け取るように注釈が付けられた関数がありstr
、 を渡すint
と、このパラメータがキャプチャされpyright
ます。
9.2 静的および動的型チェック
静的型チェックは、プログラム テキスト (ソース コード) の分析に基づいてプログラムの型安全性を検証するプロセスです。静的型チェックはコンパイル時 (プログラムの実行前) に行われます。静的型チェックを強制する言語には、C++、Java、Rust などがあります。
一方、動的型チェックは、実行時にプログラムの型安全性を検証するプロセスです。動的型チェックはプログラムの実行中に行われます。動的型チェックを使用する言語には、Python、Ruby、JavaScript などがあります。
静的型チェックでは、プログラムの実行前に型がチェックされるため、型エラーの検出と防止が容易になります。これにより、ほとんどの型関連エラーがコンパイル時に検出されるため、プログラムをより安全に実行できるようになります。ただし、プログラマはすべての変数と関数の戻り値の型を明示的に宣言する必要があるため、柔軟性が低下すると考えられます。
動的型チェックでは、各変数の型を明示的に宣言する必要がないため、柔軟性が高まります。ただし、これは実行時に型エラーが発生し、プログラムがクラッシュする可能性があることも意味します。
Python は動的に型付けされる言語ですが、タイプ ヒントなどのツールを使用したオプションの静的型チェックもサポートしています。これにより、Python プログラマは独自の柔軟性を得ることができ、静的型チェックの安全性が必要な場合と、動的型付けの柔軟性が必要な場合を選択できるようになります。pyright
10. 入力ヒントとドキュメント文字列
説明したように、型ヒントは変数、関数パラメーター、および戻り値の型を示します。これらは、他の開発者が、関数が必要とするデータの種類と関数が返すデータを理解するのに役立ちます。
一方、docstring は、関数、クラス、またはモジュールの機能説明を提供するために使用されます。docstring には、関数の目的、パラメータ、戻り値、スローされる可能性のある例外の説明を含めることができます。
タイプヒントと docstring を組み合わせて使用する方法の例を次に示します。
def filter_and_sort_products(products: list[dict[str, int]], attribute: str, min_value: int) -> list[dict[str, int]]:
"""
Filters a list of products by a given attribute and minimum value, and then sorts the filtered products by the attribute.
Args:
products (list[dict[str, int]]): A list of products represented as dictionaries.
attribute (str): The attribute to filter and sort by.
min_value (int): The minimum acceptable value of the specified attribute.
Returns:
list[dict[str, int]]: A list of filtered and sorted products.
Raises:
KeyError: If the specified attribute is not found in any product.
Examples:
>>> products = [{"name": "Apple", "price": 10}, {"name": "Banana", "price": 5}]
>>> filter_and_sort_products(products, "price", 6)
[{"name": "Apple", "price": 10}]
"""
filtered_products = [product for product in products if product[attribute] >= min_value]
return sorted(filtered_products, key=lambda x: x[attribute])
ここで、関数シグネチャは、関数が製品を表す辞書のリスト、属性を表す文字列、最小値を表す整数を取ることを示しています。フィルタリングおよびソートされた辞書のリストを返します。
doc 文字列は、関数の目的、パラメーター、戻り値、考えられる例外 (特定のプロパティが存在しない場合など) を説明し、関数の呼び出し方法の例も含まれています。KeyError
型ヒントと docstring をこのように組み合わせると、コードの可読性と保守性が大幅に向上します。
11. 結論
Python プログラミングのベスト プラクティスに基づいて、型ヒントによってコードの可読性と保守性がどのように向上するかを検証します。
ご質問がある場合、またはさらに詳しい説明が必要な場合は、お気軽に Dagster Slack に参加し、コミュニティに助けを求めてください。読んでくれてありがとう!