彷徨えるフジワラ

年がら年中さまよってます

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()

実は駄目元だったのに、これもサクっと動作しやがりましたよ!