世界を動かす技術を、日本語で。

Python開発者が型ヒントを受け入れている

概要

  • Python はAIやデータサイエンス分野で急速に普及
  • 型ヒント 導入でコード品質と信頼性が向上
  • 動的型付けと静的型付けの 違い を解説
  • PEP 484 による型ヒント導入の経緯を紹介
  • 型ヒント導入の 実践手順 とメリットを整理

Python型付け入門:なぜ今Typed Pythonが重要なのか

  • Python はGitHubのOctoverseレポートで 最も人気のある言語 となった実績
  • AI、データサイエンス、科学計算 分野での利用拡大
  • 多様なSTEM分野出身者 による開発参加
  • 動的型付け の柔軟性が初期開発や実験に最適
  • 大規模化・本番運用 に伴い、柔軟性がリスク要因となる現実

動的型付けと静的型付けの違い

  • Python動的型付け 言語
    • 変数の型が実行時に決定
    • 型宣言不要で変数に 任意の型 を代入可能
  • JavaやC++静的型付け
    • 変数宣言時に型を指定
    • 型違いの代入は コンパイルエラー
  • 動的型付け はプロトタイピングや実験に最適
  • 静的型付け は大規模開発や保守性に優位

Pythonにおける静的型付け:PEP 484の登場

  • PEP 484 (2014年提案)で型ヒントがPythonに導入
    • Python 3.5 以降で利用可能
  • 型アノテーション により関数引数や戻り値の型を明示
  • 漸進的型付け (gradual typing)を採用
    • 型ヒントは 徐々に追加可能
    • 既存コードとの 互換性維持
  • Any型 や未注釈関数の自動Any扱いで柔軟性確保

型ヒント導入のメリット

  • バグの早期発見
    • 静的解析ツールで 型不一致やエラー を実行前に検出
    • ユニットテストで見落としがちなバグも補足
  • 自己文書化
    • 関数シグネチャや変数アノテーションで 意図が明確
    • コードレビューや新規メンバーの理解が迅速
    • docstring よりも簡易・自動化が可能
  • スケーラビリティ向上
    • 実験コードから本番システム への移行が円滑
    • チーム間や開発段階間の 契約(contract) として機能
    • AIワークフローなど複雑な処理パイプラインでの型ミス防止

Typed Python導入の実践ステップ

  • Step 0 :早期導入推奨
    • プロジェクト初期から型ヒント追加が理想
    • 後からの全体導入は コスト増大
  • Step 1 :型チェッカーの選定・導入
    • 代表例: Pyrefly (Meta製・Rust実装)、 MyPy
    • ドキュメント参照で 設定やベストプラクティス を学習
    • IDE連携 でリアルタイム型チェック・補完支援
    • CI/CD組み込み でプルリクごとに型検査を実施
  • Step 2 :学習リソースの活用
    • 公式typingモジュールドキュメント
    • PEP 484 や関連PEPの精読
    • Pyrefly Docs やコミュニティ(Discord、Discourse等)

結論:Typed Pythonは未来への投資

  • 型ヒント は単なる追加機能ではなく 長期的な価値創出
  • デバッグ削減、コードレビュー円滑化、本番障害減少 という明確なリターン
  • リファクタリングやスケール 時の安心感
  • 小さな一歩 (1関数への型注釈)から始めて、型チェッカーを導入・習慣化推奨

Hackerたちの意見

自分が担当しているPythonコードにはしっかりと型を定義してるよ。他の人がdict[str, Any]みたいな曖昧な型を使うのを許さないようにしてる。そうしないと、本番環境でのトラブルを招くからね。

こういうプロジェクトに関わったことがあるけど、マジで頭が狂いそうだった。動的言語と静的言語の最悪な部分だけを持ってて、利点はゼロみたいな感じ。最初から静的型付けの言語でやりたいわ。

Pythonは手軽さが好きなんだけど、型ヒントはまだ言語に合ってない気がする。静的型付けの言語の最適化の利点がないように思える。CやJuliaを使ってる身としては(Rustに時間があればいいのに)、しっかりした型付けを導入することで、最低でもより良い結果が得られるし、逆にそれが必要な場合もある。Pythonの型ヒントはコードを読みづらくするだけだし、手軽に何かを素早くやるのが好きだったから、あんまり受け入れられてない。まだ本格的に使うほどのメリットを感じてないのかも。もしかしたら、IDEの高度な機能を使ってないからかもしれないけど、歳を取ったせいかもね :P 追記:でも、これは言語の使い方によるよね!今は大きな顧客の負荷を考える必要もないし…!

IDEの高度な機能は使ってない 自分はプラグインなしの純粋なvimを使ってるけど、型ヒントは必須だと思ってるよ。

静的型付け言語で得られる最適化のメリットはないみたいだね。 そうだね。互換性の理由から、できないんだ。特定のオブジェクトにいくつかのダンダーを設定すること(クレイジーなメタプログラミングをしない限り、全く関係ないけど)は別として、型アノテーションは実行時のコードには影響しない。Pythonのランタイムは、不正な型アノテーションのコードを喜んでバイトコードにコンパイルして実行するし、型チェックツールはそれを防ぐことができないんだ。

バグを捕まえてくれるんだ。そして、使わなくてもいい;ライブラリが提供するだけでも、ユーザーにはメリットがあるよ。

Pythonの追加の型明示がコードを読みにくくする これが面白いんだけど、私は逆だね。型アノテーションがあると、Pythonをもっと読みやすく感じる。ひとつの条件として、型チェックも行われていることを知っておく必要があるかな。そうじゃないと、アノテーションがノイズにしか思えなくなっちゃうから。だから、JuliaやRust、Cでは、型が自分を守ってくれてるって感じが強いのかもしれないね。

型ヒントがあればコードを理解するのに役立ったプロジェクトを思い出すと、今はほとんど好きだね。数日や数週間後に戻ってきて、この関数は何を出力するのか、あの関数は何を受け取るのかを思い出すのに役立つんだ。Pythonはもともとかなり型付けされてたから、型を考慮する必要があったしね。今はメモがもらえるから、結構助かるよ。

Pythonの型ヒントが増えると、コードが読みづらくなるって言うけど、「読む」って何を指してるかによるよね。もし本当に変なPython詩の夜をやってるって意味なら、確かにfibの読み方に邪魔な「余計なもの」があるかもしれない。でも大抵の人は「コードを読む」って、コードを理解することを考えてるから、その場合は確実に読みやすくなるよ。

あなたにとって型付けがコードを読みづらくするのは興味深いね。Pythonをどんなコンテキストで使ってるの?誰が書いてるの?私の経験では、ドキュメントなしで def func(data, args, *kwargs) みたいなPythonコードを見すぎて、何をしてるのか全然わからないことが多かった。今は基本的に型ヒントに全力投球してるよ(pandasみたいに不可能な場合を除いて)。

Pythonのオプショナルな型ヒントを受け入れた理由は、主にドキュメントとして価値があると気づいたから。ほんとに役立つドキュメントだよ!関数のシグネチャを見ただけで、期待される型や返される型が分かるのは超便利。

原則的にはまあまあな意見だね。でも、明らかでない型にはやっぱり苦労するよね:https://old.reddit.com/r/Python/comments/10zdidm/why_type_hi... 追記:確かに、リンターの設定によってはAnyを使うこともあるけど、それじゃ本質を外してるよね?

一番信頼できるドキュメントが一番良いドキュメントだよね。型定義を本当に信頼できなかったら、あんまり役に立たないし。逆に、ドクトテストは正確であることが確実だから、すごく有用なドキュメントだと思う。

これは素朴な認識だね。型チェックを最大限に活用すると、ユニットテストと同じくらい重要になる。コードに対する実際の安全性の貢献なんだ。多くの古い世代のPython開発者は、型がどれだけ重要かを理解していない。型はただのドキュメントじゃない。実際には開発時間を約50%削減し、安全性を約2倍に向上させることができる。

Pythonのオプショナルな型ヒントに賛成するようになったのは、主にドキュメンテーションとして価値があるって気づいたからだよ。 > でも、本当に価値のあるドキュメンテーションなんだ!関数のシグネチャを見ただけで、どんな型が期待されているか、返されるかが分かるのはめっちゃ便利だよ。じゃあ…Pythonの型ヒントを使う前にはそのことに気づいてなかったの?他の言語を使ってた時には?

型ヒントをドキュメントとして使うのは、バグ発見のための型ヒントへの入り口だね。頑張って続けて! :)

Pythonでのタイピングが大好き。C++とOOPでプログラミングを学んだんだ。Pythonを始めたときは型について気にしなくていいのが解放感だったけど、年を取るにつれて型を楽しむようになった。でも、今はちょっと行き過ぎてるよね?現代のライブラリは型を作るために型を作ってる感じがする。ネストされた型がどんどん増えて、ネイティブな型にたどり着けない気がする。ライブラリのコード例もそれを示してないし、OpenAIの例をコピー&ペーストしてLSPが喜ぶか試してみるといいよ。今は、いくつかのライブラリの型エラーを避けることに頭を使ってて、Pydanticとかがなかったらよかったのにって思うようになってる。

Pythonへの愛が、typing.TYPE_CHECKINGを知ったときに大きく傷ついた。知らない人のために言うと、Pythonは動的な性質を持ってるから、変数の型をこうやって宣言するんだ:foo: Type。これ、Typescriptみたいに見えるけど、実際には「Type」はオブジェクトなんだよね。Pythonでは、クラスや関数はファーストクラスオブジェクトだから、他の変数に渡したり、代入したりできる。ここでの明らかな問題は、通常のPythonではその行のスコープに存在するオブジェクトしか型として使えないってこと。だから、こんなことはできない: def foo() -> Bar: return Bar() class Bar: pass だって、「Bar」はfoo()の後に定義されるから、foo()が宣言されたときにはスコープにないんだよね。これを回避するために、こんな変な文字列みたいな構文を使う: def foo() -> "Bar": return Bar() これ、もう十分に見た目が悪いから、Python使いは「Python... 何やってんの?」って思うはずなんだけど、さらに悪化する。もし二つのファイルの間に循環参照があったら、Javaみたいな静的型付け言語では普通に動くけど、型ヒントを使わないPythonでは、すべてのオブジェクトが同じ「型」だから問題ない。でも、Pythonで型ヒントを使おうとすると、循環インポートになっちゃう。もっと具体的に言うと、Pythonでは通常、型が必要ないから循環インポートは必要ないけど、型ヒントを追加するためには型をインポートしなきゃいけなくて、これが型ヒントを追加するためだけに循環インポートを引き起こすんだ。これを回避するための解決策は、こんなモンスターみたいなコードを使うこと: if typing.TYPE_CHECKING: import Foo from foo これは、静的型チェックが型をチェックしているときだけ「実行」されるコードなんだ。誰もPython 4を望んでないけど、これを実装するための方法がこんなに複雑だなんて、特に今やすべてのモジュールが型ヒントを追加するために「過剰インポート」しなきゃいけないって考えると、ほんとに嫌になる。これを見るたびに、型チェックがそんなに重要なら、そもそもPythonでプログラミングするべきじゃないんじゃないかって思っちゃう。

でも、今はやりすぎじゃない? 現代のライブラリは、型を作るために型を作ってるみたい。ネイティブな型にたどり着かないネストされた型に溺れてる気がする。ちょっとTypeScriptの話をしてるのかと思ったよ。

C++を学んでからPythonを学んだんだけど、Pythonは新鮮な空気みたいだった。最初は型がないからだと思ったけど、実際には型がないことがPythonにとってはマイナスだった。これは幻想だった。Pythonがそんなに良く感じた理由は、明確なエラーメッセージとエラーやバグを見つけるための明確な道があったから。C++ではメモリリークやセグメンテーションフォルトが常に見えないところに隠れてるから、静的型付けなのに実際にはPythonよりも安全性が低くて、デバッグもずっと難しい。昔、PythonとRubyが人気になったのはトリックだった。幻想だったんだ。型がないから好きだったわけじゃない。これらの言語はCやC++じゃないから受け入れられた。型ヒントやTypeScriptが出てきて、これに気づくのに10年かかった。これは大きな技術的議論で、今では型に反対していた人たちが完全に間違っていることが証明された。

モダンC++は素晴らしい、正直に言うと。

静的型付けの支持者として、人気のある動的言語が徐々に静的型付けになっていくのが面白いと思う。何十年も「それは全く必要ない」と言われて、静的型付け言語に批判的だったのに。かなり大きなTypeScriptプロジェクトに取り組んでいたとき、依存関係に型定義があるのが普通になってた。

人は状況に適応するんだ。今や多くのPythonの使い方は、REPLでの迅速な反復作業じゃなくなってる。その代わりに、Pythonを使ってクラスターで長時間実行されるジョブやサーバー内で実行するようになってる。何時間もかけて最初からやり直す必要があるだけじゃなくて、同時実行や分散実行環境はインタラクティブプログラミングには厳しいんだ。今は例外を待ってデバッガーを起動する余裕もないし、仮に起動してもあまり役に立たない。で、個人的な意見だけど、静的型付けの方向に進むなら、Pythonに型を使うよりもScalaや似たような言語を使いたい。残念ながら、高パフォーマンスの言語(Cなど)が早すぎる最適化を引き寄せるのと同じように、静的型付けは早すぎる「抽象化」を引き寄せる(C++も同様)。動的言語は技術的なメリットの理由で最大のライブラリを持っていると思う。「流動的」であることで、ライブラリ同士を混ぜやすくなる。長期的には、エコシステムはライブラリ間の特定のインターフェースに有機的に収束していくんだ。だから、今は型ヒントや#type: ignoreがあちこちにある中途半端なアプローチになってる。

人気のある動的言語が徐々に静的型付けになってきた そのコードベースにどれだけのAny / unknown / cast / var::typeがあるか数えてみて。そうすると、実際には特に静的型付けされてないことに気づくよ。動的言語の型は大半のケースで有効性をチェックするのに役立つけど、型が複雑になりすぎると簡単に回避できちゃう。動的言語がpylintのように、実際の使用に基づいて自動的に決定された型でコードベースをチェックしなかったのは、ちょっと驚きだね。

人気のある動的言語が徐々に静的型付けになってきた これ聞いたことあるけど、実際にはそんなことないよ。確かに、JavaScriptのコードの大半は今やTypescriptを通じて静的型付けされてるかもしれない。でも、Pythonのコードの一部もそうだね(具体的な数字は知らないけど)。でもそれだけだよ。RubyやLua、Clojure、Juliaなんかで静的型付けを使ってる人はほとんどいない。

逆に、Pythonプロジェクトの一つに型ヒントを追加しようとしたときに、実はダックタイピングが好きだと気づいたんだ(でもすぐに削除したけど)。重要な型がほぼ全て和集合型で構成されてるから、静的型付けの意味がないなって思ったんだよね。例えば、Pythonが「プログラミング言語」じゃなくて「スクリプト言語」として使われるとき(主にテキストを処理する小さなコマンドラインツールを書くとき)、静的型付けはしばしば邪魔になる。静的型付けが意味を持つ大きなプロジェクトでは、別の言語を選ぶかな。正直言って、型ヒントがあってもPythonはあまり良いプログラミング言語じゃないけど、スクリプト言語としてはいい感じだよ。

Javaの極端な冗長性から来た私にとって、20年前のPythonの自由さは最高だった。混合型の複雑な構造を扱うのは楽だったしね。正しさを追跡するのは自分の責任だったけど、それがより良いコードやテストを書くことを教えてくれたんだ。

静的型付けと動的型付けの議論の中で、TypeScriptや注釈付きPythonのような解決策はあまり考慮されてなかったね。私の意見では、実行時やコンパイル時にはほとんど役に立たない複雑で推論が重い型システムのアイデアは比較的新しくて、その人気はTypeScriptの成功によるものだと思う。静的型付けの支持者たちは、HaskellやOCaml、Javaのようなものを考えていたんじゃないかな、型が消去されるシステムではなくて、[1,2] > 0が真になるような言語のことを。

そういえば、過去の有名な動的言語、Common Lisp、BASIC、Clipper、FoxProは、理由があって型ヒントを持ってたよね。その後、新しい世代のスクリプト言語がアプリケーション言語を作り出して、みんながフィールドの真ん中にフェンスがある理由を再学習しなきゃいけなくなったんだ。

徐々に静的型付けになっていく って言うけど、そうじゃないよ。徐々に型付けされていくっていうのは別のことなんだ。動的言語の利点を保ちながら、プロトタイピングのしやすさも持ってるけど、必要な時にはしっかりと制約をかけることもできる。完璧な統合ではないけど、一般的には、純粋な静的型付け言語と同じ安全レベルを達成できないか、動的な側面の表現力を捉えるために非常に複雑な型システムが必要になるっていうトレードオフがある。大抵はその両方のミックスだと思う。それでも、これが進むべき道だと思う。動的型付けが勝ったわけでも、静的型付けが勝ったわけでもなく、両方をサポートする言語があることが大きな生産性の向上になるんだ。

でも、徐々に型付けがあることで、静的型の言語よりもずっと良い体験をしてる人もいるんじゃないかな。例えば、PHPStan(コメントを通じて静的解析を追加するやつ)が好きなのは、型制約を定義する時にすごく柔軟性があるからなんだ。基本の型に加えて、関数が受け入れるリテラル値を指定することもできるし、ネストされた配列構造のサブタイプもサポートしてる(JSONをデコードした瞬間にネストされた構造を快適に型付けできる)。

それは、多くの人が本当に必要ない小さなことをやってるからだね。確かに、大きなプロジェクトをやってる人もいて、コードベースの唯一のメンテナーになったり、言語の型をユニットテストの型チェックに置き換えたりしてるけど。小さくて役に立つプロジェクト(できれば1000行未満)をサクッと作る方が、注釈を付け始めるよりもずっと早いから、拒否する理由もわかる。注釈を付けるのは、決定の迷宮にハマってビルドの流れを妨げることもあるからね。ただ、500行以上のプログラムでも型なしだと後から読むのが面倒になることがあるし、1k行近くになると再開するのが大変になることもある。結局、型なしの速さには勝てないけど、サイズと複雑さはプロジェクトを続けたり再開したりするのがどれだけ難しいかに影響する要因だね。

型のないPythonコードに型を追加する経験から、単一目的のスクリプト以上の複雑なものには静的型付けが必要だと確信したよ。古くて実績のあるコードベースでも、小さなバグや誤った仮定がたくさん見つかって、消し去られるんだ。Pythonでは完璧じゃないけど、型を「完璧」にしようとするあまり、必要ないパターンを導入する開発者もいるのを見かけるよ。でも、業界がスクリプト以上の用途でPythonに本気で取り組むなら、私たちも型付きPythonに取り組むべきだね。

実は、Pythonの型ヒントがあんまり好きじゃないんだ!うちの職場では、条件によって型ヒントが必要なJITコンパイラがあるんだけど、それ以外ではできるだけ避けてる。理由は、型ヒントが言語の一部じゃないから、言語の精神に反するし、コードの高頻度で使われる部分ではすぐにめちゃくちゃになるから。例えば、うちのコードベースでは、ある関数が整数でインデックス可能なものを受け取ることが多いんだけど、型は何でもあり得る。List、Tuple、Dict[int, Any]、torch.Size、torch.Tensor、nn.Sequential、np.ndarray、あるいはたくさんのカスタム型も!そして、許可される型はすべてこの関数に渡されることになる。時々、人は成長する許可される型のリストをUnionで注釈しようとするけど、最終的にはリストが馬鹿げたものになって、その関数は# pyre-ignoreの注釈を得ることになる。これじゃあ、無意味な作業の意味がなくなっちゃう。だから、JITコンパイラが注釈を必要とするなら喜んで提供するけど、そうじゃなければ積極的に提供しないし、時には既存の注釈が馬鹿げたものになっている時には削除することもある。

"intを取る__getitem__を持つ型"のために自分のヒントを定義できないの?

typingモジュールからProtocolとTypeVarをインポートするよ。 T_co = TypeVar("T_co", covariant=True) class Indexable(Protocol[T_co]): def getitem(self, i: int) -> T_co: ... def f(x: Indexable[str]) -> None: print(x[0]) ここでうまくフォーマットできてないけど、要はこんな感じ。

最初の頃のTypeScriptに対する不満と同じだね。Expressみたいなライブラリは、型をちゃんと表現するのが面倒な幅広い入力オプションを受け入れてたから。今のエコシステムを見てみると、ちゃんとした型スタブがあって、ほとんどのライブラリは最初からTSで書かれてるし。TSコードを編集する時は、深くネストされたプロパティや条件付き型でも自動補完が使えるから便利だよ。コンパイラが言ってる通りの型が信頼できるし、ランタイムエラーは今や珍しい(ちゃんとメンテされてるコードベースではね)。

「理由は、それらが言語の一部ではなく、言語の精神に反するからだ。そして、コードの高頻度使用部分では、すぐに完全に混乱してしまう。」 これがPythonが嫌いな理由だし、君が言う「言語の精神」ってやつだと思う。関数がどんなパラメータを取るのか、全然わからないんだ。ライブラリのドキュメントは使い方の例をいくつか見せてくれるけど、参考にはならないから、結局ソースコードを掘り下げて自分で理解しなきゃいけなくなる。型なしでドキュメントもないkwargs?どこにでもあるよ。こんなに柔軟性を受け入れる人がいるなんて理解できない。メンテナンス担当者以外には全く発見できない状態になるなんて。

言語の精神を侵害してるわけじゃないよ。オプションなんだから。ランタイムでの動作も変わらないし。型注釈を正しく使う気がないなら、確かに意味がないように見えるかもね。巨大なユニオンを使って(ジェネリック)関数の型を指定するのは確かにバカげてる。別のコメントで説明したように、その関数をジェネリックにするか、型ヒントを削除するしかないよ。

Pythonの型システムについてはよくわからないけど、こんなのはないの? interface IntIndexable { [key: number]: any }

Pythonが大好きで、2010年頃から個人プロジェクトで使ってるよ。仕事を始めて、長期間使われている知らないPythonコードベースに定期的に触れるようになって、型ヒントの利点を理解した。何が渡されているのか、何が返されているのかを理解するために5、6個の異なる関数を追いかけるのは楽しくないよ。誰かが間違えて、実際には実行パスによって異なる2つの互換性のないものになっていることを知るのはさらに楽しくない。あの時代のPythonコードベースは本当に苦痛で、しばしば「これがどう動くかわからないし、バグが多すぎるから、ただ書き直そう」っていう考えに陥ってた。

だから、タイプはインデックス関数([]、または getitem)を実装しているもので、これはシーケンスに似てると思う。Iterableと同じ感じだね。

from typing import Sequence def third(something: Sequence): return indexable[3] でも、もしただそのものを繰り返し処理してるだけなら、実際に必要なのはIterableだよ。 from typing import Iterable def average(something: Iterable): for thing in something: ... 統計的に言えば、言語が間違っている可能性は、プログラマーが間違っている可能性よりもずっと低い。Pythonに対する有効な批判がないわけじゃないけど、プログラミング言語の創造者たちとその作品は、やっぱりこの分野のトップだと思う。1400のチェスELOのプレイヤーがマグヌス・カールセンのチェス理論を批判しても、そのプレイヤーが理論を見落としている可能性の方が高い。問題に対して自分が問題だというメンタリティでアプローチした方が、プレイヤーには良い結果が得られると思うよ。

君は、すべての許可される型が最終的にこの関数に渡されると信じるべきだ それが君の問題だよ。なんでランダムな呼び出し元が異なる入力型をその関数に送ってるの?とはいえ、そのプロパティを型として定義する方法はいくつかあるから、「Indexable」というプロトタイプ型を使ってみたらどう?

え、Pythonの型には「整数でインデックスできて、そのインデックス演算子がTを返す何か」を表現する方法がないって言ってるの?

俺の経験では、正しいツールを使うとPythonの型付けがめっちゃ便利になる。現代のIDEは、型エラーに対してリアルタイムで詳しいフィードバックをくれるから、生産性が大幅にアップするし、微妙なバグも早めに見つけられる(Rustには及ばないけど、それでも価値はあるよ)。でも、やりすぎると、Callable[[Callable[P, Awaitable[T]]], TaskFunction[P, T]]みたいなモンスターが生まれちゃうから、どのくらい型を使うかのバランスが大事だね。

Pythonの動的特性は、いくつかのことを正しく表現するのがかなり難しいことがある。あるいは、型チェッカーが他の言語で安全と見なされるものを理解するのに問題があるのかも。数年前、型やプログラミングについてあまり知らなかった頃、Javaではそんな問題に直面したことはなかった。時には馬鹿げていたけど、いつも何かを表現する方法を見つけていた。ただ、もしかしたら、私は推論と安全性にもっと求めているのかもしれない。最近、ステップのパイプラインが欲しかったけど、ステップは前のステップの型と一致する限り、どんな入力と出力の型でも良くて、型チェッカーは最終的な出力型も知っているべきだし、すべてのステップを一度に追加しなくても良くて、ステップバイステップでパイプラインを構築できるようにしたかった。何時間も試したけど、型チェックが通る解決策は見つからなかった。LLMの助けを借りても、見た目は素晴らしいコードが得られたけど、どこかで型エラーが発生して、修正に苦労した。結局、ステップ間の型チェックとパイプラインの出力型については諦めた。何時間も投資したのに、実現不可能か、得られるものに対しては作業が多すぎると気づいたから。型注釈がなければ、こんなことに時間をかけずに、単純な動的解決策に進んでいただろうな。

それはPythonの動的な性質とは関係ないように聞こえるね。型チェックはソースコードの静的解析だから、もし何かを動的に推論させたいなら、ジェネリクスを使う必要があるよ。

from typing import Callable class Pipeline[T]: def init(self, value: T) -> None: self._value = value def step[U](self, cb: Callable[[T], U]) -> 'Pipeline[U]': return Pipeline(cb(self._value)) def terminate(self) -> T: return self._value def _float_to_int(value: float) -> int: return int(value) def _int_to_str(value: int) -> str: return str(value) def main() -> None: result = Pipeline(3.14) .step(_float_to_int) .step(_int_to_str) .terminate() print(result) if name == 'main': main() 型変数を使ってジェネリック型をさらに制約することもできるよ。 https://docs.python.org/3/library/typing.html#typing.TypeVar

型は、比較的少ない投資で良いプラクティスを促進する素晴らしい方法だと思う。型安全を提供して、実行中のドキュメントのように機能し、プロダクションでの保護の層を追加してくれる。ただ、大規模なコードベースでは、一貫性が課題になることがある。異なる開発者が同じ問題に対して異なるアプローチを取ることが多くて、特に明確な標準がない場合や問題自体が複雑な場合、型パターンやスタイルが混在しちゃう。LLM生成のコードが増えると、この問題はさらに顕著になるよ。適切な規約に従わないと、コードの質や職人技が簡単に劣化しちゃうからね。

Pythonの型って、静的型の負担は全部背負ってるのに、パフォーマンスはゼロだよね!pydanticみたいなパッケージや、シンプルな静的型は好きだけど、もし本当にOOPを実装するなら、最初にPythonを選ぶことはないかな。言語自体が複数のコンストラクタや公開/非公開のプロパティをサポートしてないし。 追記:ちなみに、もっと冗長な型指定が必要な場合、変数のような構文で型を定義することもできるって知って面白かった。 mytype = int|str|list|etc.

型の重要な(というか主な)利点は、パフォーマンスじゃなくて、プログラムの構造を[あなた | あなたのIDE | LLM]が理解できるようにすることだと思う。

最初から「本当にOOP」を実装するべきじゃないよ…

「複数のコンストラクタ」って、外側のクラスの型のオブジェクトを返す複数の静的メソッドから得られない何かをもたらすの?もしかして、俺が何かクールなことを見逃してるのかな…