2010-03-21

_ ファイルシステムの垣根を越える

現代的な開発と言えば、Webベースですが、 残念ながら、既存のソフトウェアはファイルシステムに固執するコードが大量にあって、なかなか一筋縄では済まないことがたくさん生じてきます。 例えば、DSL的な考え方で、設定から簡単にクラスを作成して、動作をカスタマイズしたくなったりしますが、その場合、次のような障害にぶちあたります。

  • 普通はファイルをロードして、静的に定義されたクラスをインポートすることを想定しているので、ここを動的に定義できるようにしないといけない。
  • 元になる情報がすぐに利用できるとは限らないというブートストラップ問題が生じるので、どうにかして初期化が終わるまで遅延させる手段が必要になる。

最初の問題は、必殺evalという手段もありますが、もうちょっと器用に操作したいこともあります。

二番目の問題は、コード中でインポートするのを遅らせるという手段もありますが、それだとプログラマが常に気をつけないといけないので、ちょっと大変です。

実のところ、Pythonなら、そう大した苦労をしなくても対処することができるので、簡単な実装を試してみました。 まず最初に問題に対しては、動的にオブジェクトを作ってインポートできるモジュールを。

from types import ModuleType
import sys

class DynamicModule(ModuleType):
    """This module may generate new objects at runtime."""

    def __init__(self, name, factory, doc=None):
        super(DynamicModule, self).__init__(name, doc=doc)
        self._factory = factory

    def __getattr__(self, name):
        if name == '__path__':
            raise AttributeError('%s does not have __path__' % (name,))
        obj = self._factory(name)
        if hasattr(obj, '__module__'):
            obj.__module__ = self.__name__
        setattr(self, name, obj)
        return obj

def dynamicmodule(name, factory):
    d = DynamicModule(name, factory)
    sys.modules[name] = d
    return d

そうすると、こういうことができるようになります。

def factory(name):
    return name
dynamicmodule('dynamic', factory)
import dynamic
print dynamic.foo

fooというオブジェクトはどこにも静的に定義していませんが、自動的に作成されます。 これで第一段階終了。

次に、二番目の問題。 すぐにロードできるとは限らないので、 本当に使うまで初期化を遅らせ、 後からクラスの継承関係やクラス自身の属性をいじれるような、 幽霊クラスを作ります。

def lazyclass(name, factory):
    class Ghost(object):
        def __getattribute__(self, attr):
            if attr[:2] == '__' and attr[-2:] == '__':
                return super(Ghost, self).__getattribute__(attr)
            klass = super(Ghost, self).__getattribute__('__class__')
            baseclasses, attributes = factory(klass.__name__)
            klass.__bases__ = baseclasses
            for key, value in attributes.iteritems():
              setattr(klass, key, value)
            return getattr(self, attr)

    klass = type(name, (Ghost,), dict())
    return klass

こうすると、例えば、こんなことができるようになります。

class Base(object): pass
def factory(name):
    return (Base,), dict(name=name)
Foo = lazyclass('Foo', factory)
foo = Foo()
print foo.foo

foo.fooは最後の行に辿り着くまでは作られません。 ちなみに、わざわざBaseを作っている理由は、 Issue 672115: Assignment to __bases__ of direct object subclasses に書いてあるPythonのバグのせいです。 最近のPythonでもまだ直ってないので、objectを直接使うことができません。

この二つを組み合わせれば、ファイルシステムに存在せず、かつ、静的定義なしに、新しいクラスやその他のオブジェクトをインポートしたり、使ったりすることができるようになります。

ちなみに、最初の問題に対しては、 plone.alterego でも同じようなことができますが、 そのままだとpickleできないオブジェクトができてしまうので、 自分で書いてしまいました。 また、二番目の問題に対しては、 ZODB にも似たようなコードが入っています。 実際、ZODB依存でよければ、zodbcode を使うと、いろいろ変なことができるようになります。

[]

トップ «前の日記(2009-12-24) 最新