現代的な開発と言えば、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 を使うと、いろいろ変なことができるようになります。