読者です 読者をやめる 読者になる 読者になる

Pythonでマジックメソッド

python デザインパターン

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

前項でBuilderパターンについて書きましたが、
ComputerBuilderを作っても、まだ、

builder.set_turbo()
builder.add_dvd()
builder.add_harddisk()

と、何度もメソッドを呼ばなくてはなりません。

その一つの解決策として『Rubyによるデザインパターン』では、マジックメソッドを紹介しています。

#マジックメソッドを使えば、こんなふうに書く事が出来ます。
builder.add_turbo_and_dvd_and_harddisk()

マジックメソッドは、__getattr__を使って、簡単に実装できます。

class ComputerBuilder(object):
    def __getattr__(self, name):
        words = name.split("_")
        
        if "add" != words.pop(0):
            return super(ComputerBuilder, self).__getattr__(name)
        
        words = [w for w in words if w != "and"]
        
        functions = dict(
            cd=self.add_cd,
            dvd=self.add_dvd,
            harddisk=lambda :self.add_harddisk(1024*1024*1024),
            turbo=self.set_turbo,
        )
        
        for w in words:
            if w not in functions:
                raise AttributeError("wrong magic method word %r"%(w,))
        
        def f():
            for w in words:
                functions[w]()
        return f
#使い方
builder.add_turbo_and_dvd_and_harddisk()

しかし、これは分かりにくい!!

まず、add_turbo_and_dvd_and_harddiskメソッドの実装を調べようとしたユーザーは、__getattr__のマジックの存在に気づくまで、右往左往するでしょう。

また、__getattr__が返している f がメソッドオブジェクトではなく、関数オブジェクトであるとか、docstringが付いていない等の問題もあります。、

こんな黒魔術を使うより、素直にPythonの機能を使って・・・

class ComputerBuilder(object):
    def add(self, items):
        for word in items:
            if word == "and":
                continue
            elif word == "cd":
                self.add_cd()
            elif word == "dvd":
                self.add_dvd()
            elif word == "harddisk":
                self.add_harddisk(1024*1024*1024)
            elif word == "turbo":
                self.set_turbo()
            else:
                raise ValueError("unexpected device name %r"%())
#使い方
builder.add("turbo dvd harddisk".split())


と、するか、キーワード引数を活用した方が良いと思います。