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

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

Pythonでは、クラス内で定義することの出来るメソッドが三種類用意されている。メソッド・クラスメソッド・スタティックメソッドの3種類だ。それぞれどんな特徴を持っているのだろうか?

メソッド

まず、メソッドはおなじみのインスタンスメソッドで、第一引数としてインスタンスを受け取り、呼び出す時には必ずインスタンスが必要となる。

class Foo(object):
    # メソッド
    def method(self, arg):
	pass

Foo().method("arg") #OK
Foo.method("arg") #これはエラー
クラスメソッド

クラスメソッドは第一引数としてクラスオブジェクトを受け取り、インスタンスが無くとも呼び出すことが出来る。

class Foo(object):
    # クラスメソッド
    @classmethod
    def clsmethod(cls, arg):
	pass

Foo().clsmethod("arg") #OK
Foo.clsmethod("arg") #OK

ここで、クラスメソッドが受け取るクラスオブジェクトは、クラスメソッドが定義されたクラスではなく、クラスメソッドを参照する時に使用されたクラスとなる点に注意が必要だ。例えば

class Base(object):
    @classmethod
    def clsmethod(cls, arg):
	print cls

class Derived(Base):
    pass

で、Base.clsmethod() とすれば clsには Baseオブジェクトが、Derived.clsmethod() とすれば cls には Derivedオブジェクトが渡される。

スタティックメソッド

スタティックメソッドでは引数は特に追加されず、インスタンスが無くとも呼び出すことが出来る。

class Foo(object):
    # スタティックメソッド
    @staticmethod
    def statmethod(arg):
	pass

Foo().statmethod("arg") #OK
Foo.statmethod("arg") #OK

で、

簡単に解説したところで本題に入るが、スタティックメソッドの存在意義ってなに?必要なの?という話だ。見た目上、スタティックメソッド と クラスメソッド の違いは第一引数にクラスオブジェクトが渡されるかどうかだけで、クラスメソッドだけあれば良いんじゃない?と思える。クラスオブジェクトが不要なら使わなければ良いだけだし。

実際その通りで、どうしてもスタティックメソッドを使わなければならない状況というのは存在しない。少なくとも、これまでの私のPythonista人生でそんな状況に当たったことはないし、当たったという話も聞いたことがない。

しかしながら、細かく見ればクラスメソッド よりもスタティックメソッドを使った方がちょっとだけ良いと言えなくもない状況が存在しないわけではないので、思いついたのを書いてみよう。

スタティックメソッドはちょっとだけ速い

メソッドやクラスメソッドを呼び出す時、 Pythonの内部では呼び出しごとにBoundオブジェクトを新しく生成してから実行するが、スタティックメソッドではBoundオブジェクトを介さず、直接関数オブジェクトが実行される。このため、スタティックメソッドの呼び出しは他のメソッド呼び出しよりも高速となる。

class Foo(object):
    # メソッド
    def method(self, arg):
	pass
    # スタティックメソッド
    @staticmethod
    def statmethod(arg):
	pass

では、Foo().method() よりも Foo().statmethod() のほうが速い。パフォーマンス上、可能であればメソッドをスタティックメソッドにする価値がないこともない。しかし、そんなことをしなくても昔ながらの高速化手法であるメソッドのキャッシュを使えば、Boundオブジェクトの生成を回避しつつ名前解決の無駄までも省ける。あまり意味のない最適化である。

class Foo(object):
    # メソッド
    def method(self, arg):
	pass

obj = Foo()
method = obj.method  # メソッドを先に取得し、ループ中での名前解決・オブジェクト生成を回避する
for i in range(10000):
    method()

Boundオブジェクトが生成されないことから、たとえばオブジェクトのメソッドを別のコンテナにたくさん登録した時のメモリ消費が少ないとか、循環参照が発生しにくいのでGCが速いとかあるかもしれないが、クラスにもインスタンスにもアクセスすることが出来ないスタティックメソッドをそんな使い方するだろうか?あまり現実味がないように思える。

外部の関数をクラスに取り込みやすい

例えばアプリケーションのバージョン情報が versionモジュールの get_version関数で得られるとすると、

import version
class App(object):
    def get_version(self):
	return version.get_version()

とかするのが普通だが

import version
class App(object):
    get_version = staticmethod(version.get_version)

と書くことも出来る。ちょっとだけ短くはなっているが、どうでも良いといえばどうでも良いレベルだ。

で、結局 スタティックメソッドは要らない子なの?

少なくとも私には必要のない子です。「スタティックメソッド?なにそれおいしいの?」と聞かれれば「まずいから気にしなくって良いよ」と答えます。

しかし、スタティックメソッドが不要なら、なぜPythonに実装されたのだろうか?想像だが、Python開発者 Guido von Rossum の 「勢いで、つい」って感じではないかと思っている。

Python 2.2でオブジェクトシステムに大規模な修正が加わり、オブジェクトのディスクリプタという機能が追加された。スタティックメソッドやクラスメソッド、プロパティはこのディスクリプタを利用して実現した機能だが、このときGuidoは内心「クラスメソッド、プロパティの二つは決まりだけど、二つだけというのはいかにもさみしい。スタティックメソッドみたいなことが出来るってはっきりさせておきたいし」とか思ったんではないだろうか。そして、「よし、いれちゃえ」と。

しかしながら、この想像が真実なら、なぜPython3.xでスタティックメソッドは生き残ったのだろうか? 私はPython3ではスタティックメソッドは削除され、クラスメソッドに一本化されると予想していた。しかし、スタティックメソッドは生き延びたのである。削除が議論されたという形跡も見つからない。

もしかすると、スタティックメソッドには私の知らない秘密が隠されているのであろうか?便利な使い方が潜んでいるのだろうか?若干の不安を覚えつつ、本稿はここに終える。