このBlogは移転しました。今後は aish.dev を御覧ください。

スタティックメソッドは要らない子? - その2

http://d.hatena.ne.jp/atsuoishimoto/20100807/1281169026 を書いたあとに、スタティックメソッドの使い道を思いついた。やや人工的なシチュエーションではあるが…

こんなコードを考えてみよう

def _callexit():
    sys.exit()
    
class Foo(object):
    shutdown = _callexit
    
Foo().shutdown()

このコードは動作しない。 Foo().shutdown() のところで、 TypeError: func() takes no arguments (1 given) と怒られてしまう。クラスに関数オブジェクトを登録すると、それはインスタンスメソッドとみなされ、必ず self付きで呼び出されてしまうのだ。

従って、たとえばクラスごとに外部からコールバック関数などを設定して利用するようなパターンでは、コールバック関数を登録する時にstaticmethodを使って

class Foo(object):
    @classmethod
    def set_callback(cls, callback):
        cls.callback = staticmethod(callback)

とすると良いだろう。

ところで、

def _callexit():
    sys.exit()
    
class Foo(object):
    shutdown = _callexit

はエラーとなるが

class Foo(object):
    shutdown = sys.exit
    
Foo().shutdown()

は正常に動作する。なにが違うのだろうか?

>>> type(_callexit)
<type 'function'>
>>> type(sys.exit)
<type 'builtin_function_or_method'>

そう、Pythonで書いた普通の関数 _callexit()function 型だが、sys.exit()C言語で書かれた組み込み関数で、また別の種類のオブジェクトなのである。そして組み込み関数はインスタンスメソッドとは見なされないため、余計な引数の追加なしで呼び出すことが出来るのだ。 同様な理屈で、

class Shutdown:
    def __call__(self):
        sys.exit()

class Bar:
    shutdown = Shutdown()

もOKだ。

だが、

class Foo(object):
    shutdown = sys.exit

は最良のコードとは言い難い。動くことは動くが、いつの日かsys.exit()が普通の関数になってしまうかも知れないし、あるいはsys.exit()ではなく、別の関数オブジェクトを呼び出すように変更されるかも知れない。JythonやPyPyなど、CPython以外での挙動も気になるところだ。

そこで、もしこんなコードがあったら、こっそりと

class Foo(object):
    shutdown = staticmethod(sys.exit)

と書き直しておくと良いだろう。 staticmethodでラップしておけば中身がどんな種類のオブジェクトであってもちゃんと呼び出すことが出来るし、見た目上のシンプルさも保つことが出来る。