Python 3.12のGenerics(ジェネリクス)の書き方がクリーンになった記憶はまだ新しいです。
ですが、実際にPythonプロジェクトでGenericsを使っていないケースも多く見受けられます。「使わなくても実装できる」という前提はあるものの、Genericsはより汎用的かつ実用的なコードを書く際にとても便利です。
このコンテンツでは、「PythonのGenericsってなんぞや」という人向けにPythonのGenericsについて簡単に紹介します。
そもそもGenerics(ジェネリクス)とは
Genericsとは、汎用型(または総称型)と呼ばれる汎用的な型に対応できるクラスや関数を作れる機能のことを指します。TypeScriptやJava、最近ではRustで馴染みのある機能です。
わかりやすく一旦TypeScriptで書くと、以下のような形になります。
function get_str(val: string): string {
return val
}
function get_num(val: number): number {
return val
}
console.log(get_str('Hello World!')) //-> Hello World!
console.log(get_num(42)) //-> 42どちらの関数も同じく引数のvalを返すだけですが、型が違うことによって別々の関数として定義しなくてはいけません。これを汎用的な関数にしたいため、Genericsを利用して書き直します。
function get_val<T>(val: T): T {
return val
}
console.log(get_val<string>('Hello World!')) //-> Hello World!
console.log(get_val<number>(42)) //-> 42このように、Genericsを使うと関数実行時に型を定める書き方ができるようになります。
Genericsを使うメリットと注意点
なぜGenericsを使うと嬉しいか、いくつかのポイントがあります。以下がGenericsを使うメリットとなります。
- 省コード化に役立つ
- テストが減る(クラスや関数ごとに型でfailするテストケースを書かなくていい)
- TypeScriptでいう
any、Pythonでいうtyping.Anyの撲滅に貢献
linterでコードチェックしているときに引っかかる問題へ役立ちます。
ただし、注意したほうが良いのは、正しい用法を理解した上で利用することです。
複数の型に対応できるということで、あやふやコード設計の上でGenericsを使い倒してしまうと、最終的に型安全ではないコードが量産されてしまいます。
それゆえ、Genericsは限定的な使い方をするのをおすすめします。
PythonのGenericsの簡単な構文
ここで本題となりますが、PythonによるGenerics構文を見ていきましょう。
Python 3.12のバージョンアップでGenericsの書き方が改善されたため、旧バージョンの書き方も含めて紹介します。
まずは、Python 3.11以前の書き方を紹介します。
from typing import TypeVar
T = TypeVar('T', int, float, complex)
def f(x: T) -> T:
return x
print(f(27))見て分かる通り、冗長な書き方をするしか方法がありませんでした。
これがPython 3.12で以下のように書けるようになりました。
def get_val[T](x: T) -> T:
return x
print(get_val(27))class A[T]:
def get_val(self, x: T) -> T:
return x
a = A[int]()
print(a.get_val(27))このように、現行最新バージョンのPythonでは、TypeScriptなどに近い書き方が可能です。
Python記法の進化
Pythonは静的型付け言語ではないため、真のGenericsと呼べるかは微妙なラインです。しかしながら、タイプヒンティングをより取り入れたコーディングのために、新たな記法が生まれてくれたおかげでPythonらしさが保たれたように感じます。
Pythonコーディングに慣れていると、とっつきにくいと感じやすいですが、便利な機能なのでぜひ導入してみてください。





