ちょっと黒い自動委譲
ところで、自動委譲と__getattr__の方法1・方法2にはちょっとした欠陥があります。
w = WriterDecorator1(SimpleWriter("test.txt")) print(w.writeline) # => <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で使われていたりします。