Python におけるクラス定義の遅延
例によって英語力がアレなので、購読しつつもそれほどちゃんと読んでいるわけではないMercurial 開発者向け ML なのだけど、ある日つらとらと眺めていると、他の人のメールなのに、何故か自分の名前を見かけた気が.....
そんな筈ないよねぇ?と思いつつ、念のためメールをよくよく読んでみると...俺のパッチで性能劣化かよ!? ... orz
あっちゃー、通常のシンボル参照と違って、クラス定義の場合はモジュールローディングが実施されちゃうのね > ondemand import @ Mercurial
とりあえず、(1) クラス定義を別ファイルへ移動する方法と、(2) クラス定義の実施を遅延させる方法で、性能改善を確認してみることに。
(1) に関しては、i18n.py とか encoding.py とかいろいろ移動してみたけど、結局完全に別なファイルに移動させないと、間接参照契機でクラス定義が走ってしまう、ということが判明。ま、しょうがないか。
で、ヘタレ Python-ista な自分としては、(2) に関してはイマイチ遣り方がわからない状態。
とは言っても、関数内関数定義とか関数内クラス定義とかあるぐらいだし、Python のシンボルが基本的には「何でも参照可能な変数」であることはわかっているので、クラス定義も同じように扱えるんじゃねーの?という見通しはあったので、とりあえず簡単な例で検証してみることに。
# 初期段階は「クラス Hoge 未定義」状態に Hoge = None def showit(text): global Hoge if not Hoge: # 未定義状態の場合 # クラス定義を実施 class internal: def __init__(self, a): self.a = a def showit(self): print self.a # シンボル Hoge が内部クラス internal のクラス定義を参照 # ⇒ クラス Hoge が定義完了 Hoge = internal Hoge(text).showit()
こんな感じで試しに作ったクラス定義の遅延処理だけど、あっさり動いてしまった!うわ、簡単だな!
心配になって、internal クラス定義の直前にメッセージ表示を入れてみたけど、1回しか実行されていないから、ちゃんと Hoge クラスは定義されたことになっているみたい。
internal クラスの名前空間っつーか完全名とかってどうなるんだろ?と思って internal クラスを print してみたところ、なんだか普通にグローバルスコープで定義されている感じ。
いろいろ試した結果、Python の関数内関数とか関数内クラスは全てグローバルスコープ(ないし定義モジュールスコープ?)配下に直接ぶら下がることを確認。インナークラスの完全名がアウタークラスにぶら下がる形式になる Java とは違うのだな。
もしも「クラス定義 = 変数の定義 + 生成したクラスオブジェクトの代入」だとしたら、別に internal なんて一時クラス名で定義しなくても、直接クラス定義したら変数の参照先を変更してくれるんじゃね?と思い、当初の実装を更に以下のように変更:
Hoge = None def showit(text): global Hoge if not Hoge: class Hoge: # クラス定義 = 変数の書き換え def __init__(self, a): self.a = a def showit(self): print self.a Hoge(text).showit()
実は駄目元だったのに、これもサクっと動作しやがりましたよ!