PendulumがPython史上最も呪われた「+」演算子を書かざるを得なかった理由
Why Pendulum had to write the most cursed "+" operator in all of Python
Pendulumの「+」演算子は、呼び出し元の関数名をスタックから調べて動作を変える。標準ライブラリのdatetimeを継承しつつDST対応の演算を提供するという2つの約束の衝突が、このハックを生んだ。筆者は自身のライブラリwheneverの開発中にこの問題に遭遇し、コールスタックを辿る代わりに標準ライブラリに素のdatetimeを渡す修正を提案。Pendulumにマージされ、次のリリースで「+」が約5倍高速化する見込みだ。
Pendulumは板挟みになっていた。変換ロジックを変えることはできず(それは標準ライブラリの領分だ)、「+」のオーバーロードを外すこともできず(それではDST対応の演算という約束が破られる)、サブクラスであることをやめることもできなかった(それではドロップイン互換という約束が破られる)。
HNでの議論
14- shoo
うーん。コールスタックを内省して制御フローを変えるのは、まあまあ厄介だね。
コールスタックの内省がプロジェクトでうまく活用できた例を思い出すよ。制御フローを変えるためじゃなくて、ログを改善するためだった。open_db_connection というユーティリティ関数の中で、コードベースのどの部分がコネクションを開いたのかを説明する情報的な名前を自動生成するのに使っていた。
例えば、some_backend_process: main -> ... > grandparent -> parent -> open_db_connection みたいな感じで。
コールスタックの情報を使って、呼び出し箇所を表す "some_backend_process grandparent.parent" という文字列を生成し、コネクション確立時に application_name [1] として postgres に渡せるようにしていた。そうするとデータベース側から見て、実行時に問題のあるクエリがあった場合に、バックエンドのコードベースのどの部分が原因なのかを突き止める手がかりが格段に増えるんだ [2]。
コールスタックを覗かずに同等のことをやる方法もある。呼び出し側に説明的で一意な名前を明示的に渡してもらうようにするんだ。でもそれだとミスが起きやすくなる。特に、既存コードの一部をコピペして、どこでも同じ名前を再利用しがちな開発者もいるからね。
[1] https://www.postgresql.org/docs/current/libpq-connect.html#L...
[2] https://www.postgresql.org/docs/current/monitoring-stats.htm...
- jvolkman
俺のハックでさらに上を行くよ。呼び出し元に応じて `sys.platform` の値を変えるやつだ: https://github.com/jvolkman/rules_pycross/blob/main/pycross/...
これはバイナリホイールをクロスコンパイルするとき(例えば linux のビルドホストで macos のホイールをビルドする、あるいはその逆)に使っている。
- ariebovenberg
作者です。Pendulum を、自分がメンテしている datetime ライブラリ `whenever` と比較しているときにこれに遭遇しました。ハックを削除するパッチは送りましたが、この記事は、なぜ根底にある設計が同じようにはパッチできないのかについてのものです。質問があれば喜んで答えます。