ちょっと黒い自動委譲

ところで、自動委譲と__getattr__の方法1・方法2にはちょっとした欠陥があります。

w = WriterDecorator1(SimpleWriter("test.txt"))
print(w.writeline) 
# &#061;> <bound method SimpleWriter.writeline of <SimpleWriter object at 0x00B008B0>>

WriterDecorator1のメソッドのはずなのに、SimpleWriterのメソッドの名前が表示されてしまいます。さらに、WriterDecoratorのメソッドをとってくる事も出来ません。

WriterDecorator2.writeline
#=> AttributeError: type object 'WriterDecorator2' has no attribute 'writeline'

方法1も方法2も本当は、WriterDecoratorのメソッドを定義しているわけではないからです。そこで、本当にWriterDecoratorのメソッドを定義してみます。

方法3:execその1

def set_deligates(class_, attr_name, deligates):
    """委譲メソッドを自動定義する関数"""
    for method_name in deligates:
        defstr = ("""def %(method_name)s(self, *a, **kw):
                    return self.%(attr_name)s.%(method_name)s(*a, **kw)""" % 
                    dict(method_name=method_name, attr_name=attr_name))
        exec defstr
        setattr(class_, method_name, eval(method_name))

class WriterDecorator3(object):
    def __init__(self, writer):
        self.writer = writer

set_deligates(WriterDecorator3, "writer", 
              "writeline pos rewind close".split())

print WriterDecorator3.writeline 
#=> 

set_deligatesの中で、exec文を使っています。これは、文字列が、あたかも本当にソースコードに書き込まれているかのように実行します。defstrは関数定義の文字列になりますが、それが実行されると本当に関数が作られます。

evalも同様で、文字列が表すPythonの式を返します。たとえば、eval("1 + 3") == 4 ですし、eval("writeline")はwriteline関数を返します。そして、出来た関数をclsss_にセットしています。

方法4:execその2

方法3の欠点は、set_deligatesの呼び出しが、クラス定義の外にあることです。メソッドの定義は、やはり、クラスの内側に書きたいですよね。Rubyにはそういう関数(メソッド)があるので、対抗して書いてみます。

def def_deligates(locals, attr_name, deligates):
    for method_name in deligates:
        defstr = ("""def %(method_name)s(self, *a, **kw):
                    return self.%(attr_name)s.%(method_name)s(*a, **kw)""" % 
                    dict(method_name=method_name, attr_name=attr_name))
        
        exec defstr in {}, locals

class WriterDecorator4(object):
    def_deligates(locals(), "writer", "writeline pos rewind close".split())
    def __init__(self, writer):
        self.writer = writer

クラス定義の中では、クラスそのものを手に入れることが出来ないので、代わりにlocals()を渡しています。メソッド定義とは、結局、クラスの中にある関数の定義の事です。

exec defstr in {}, localsとは、localsの中でdefstrを実行する事になります。

また、execやevalは実行が遅いという欠点があります。
クラス定義は、普通1回しか実行されないので、問題にならないですが。

また、execやevalは何でも出来てしまうので、わかりにくくなりがちです。

そのため黒魔術とさえ呼ばれます。しかし、実はturtle や collections.namedtupleで使われていたりします。