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