メタプログラミングパターン (2) メソッドの自動定義

Rubyによるデザインパターン』(ラス・オルセン著 ピアソン・エデュケーション刊)の例をPythonに変換して書いています。目次

メソッドを自動的に定義する関数を作ります。

生態系のシミュレータを作ります。

そこにはトラが住んでいるのですが、トラには2つの情報があります。
つまり『居住地』と『生物種』です。

『居住地』も『生物種』も、ツリー構造で体系化できます。たとえば、トラという種は

動物界 -> 脊索動物門 -> 哺乳綱 -> 食肉目 ->ネコ科 -> ヒョウ属 -> トラ

と表せますが、脊索動物門には人間やサンマやニワトリや様々な動物が含まれています。


『居住地』も地球 -> 日本 -> 宮城県 -> 仙台市 -> 青葉区 -> 八木山 -> 八木山動物園

と、ツリーで表せます。

そして、トラのインスタンスであるトニー君は、
『居住地』と『生物種』両方のツリーの葉要素になっています。

では、コーディングに入りますが、『居住地』と『生物種』で、同じようなツリー構造のコードを書きたくはありません。今回は2つのツリーですが、場合によっては3つ、あるいは16のツリーの葉要素になることがありえます。


そこで、execを利用してコードを自動生成します。

def member_of(loc, composite_name):
    defstr = ("""\
_parent_%(composite_name)s = None

def get_parent_%(composite_name)s(self):
    return self._parent_%(composite_name)s
    
def set_parent_%(composite_name)s(self, v):
    self._parent_%(composite_name)s = v""" 
    % dict(composite_name=composite_name,))
    
    exec defstr in {}, loc
    
    
def composite_of(loc, composite_name):
    member_of(loc, composite_name)
    
    defstr = ("""\
def get_sub_%(composite_name)s(self):
    if not hasattr(self, "_sub_%(composite_name)s"):
        self._sub_%(composite_name)s = []
    return self._sub_%(composite_name)s
    
def add_sub_%(composite_name)s(self, child):
    if not child in self.get_sub_%(composite_name)s():
        self.get_sub_%(composite_name)s().append(child)
        child.set_parent_%(composite_name)s(self)
        
def del_sub_%(composite_name)s(self, child):
    if child in self.get_sub_%(composite_name)s():
        self.get_sub_%(composite_name)s().remove(child)
        child.set_parent_%(composite_name)s(None)"""
    % dict(composite_name=composite_name,))
    
    exec defstr in {}, loc

class CompositeBase(object):
    def __init__(self, name):
        self.name = name
    
    def __str__(self):
        return "%s(%s)"%(self.__class__.__name__, self.name)

class Tiger(CompositeBase):
    member_of(locals(), "population")
    member_of(locals(), "classification")

class Tree(CompositeBase):
    member_of(locals(), "population")
    member_of(locals(), "classification")

class Jungle(CompositeBase):
    composite_of(locals(), "population")
    
class Species(CompositeBase):
    composite_of(locals(), "classification")

if __name__ == "__main__":
    tony_tiger = Tiger("トニー")
    
    species    = Species("トラ")
    species.add_sub_classification(tony_tiger)
    
    se_jungle = Jungle("南東の森")
    se_jungle.add_sub_population(tony_tiger)
    
    print tony_tiger,
    print tony_tiger.get_parent_classification(),
    print tony_tiger.get_parent_population(),

exec文は、文字列をあたかも実際にファイルに書かれたコードの様に実行します。

exec some_string in globals_dict, locals_dict

のように書くのですが、globals_dictにはグローバル変数の一覧が入った辞書、
locals_dictにはローカル変数の辞書を指定します。

そして、locals関数を使うと、その場所のローカル変数一覧を取得できます。

locals()を渡すと、member_ofやcomposite_ofはクラス定義の中にメソッドの定義が書かれていたかのように、実行します。

見ればわかるとおり、execを使ったコードはギョッとします。execに限らず、メタプログラミングは難解になりがちなので、多用は危険です。