Python classmethodとstaticmethodを使う意味を考える

Web開発

Pythonのclassについて学んでいくと、インスタンスメソッドだけでなくクラスメソッドとスタティックメソッドなるものの存在に出会います。

これらの実装方法についてはいろんな記事で紹介されていますし、実装自体はデコレータを一つ書くだけで簡単なので、使うだけならすぐに使えます。

ただ、なぜわざわざこれらを使い分けるのか?についての記事には出会ったことがありませんでした。

ずっと疑問だったclassmethodとstaticmethodの使い分けについて、そもそもなぜ使い分ける必要があるのか?なんのメリットがあるのか?について、ようやく自分の中で腑に落ちてきたので気持ちをメモしておきたいと思います。

classmethodとstaticmethodとは

軽くおさらいしておきます。

classmethod

クラスメソッドです。以下の様に@classmethodをクラス関数につけるだけです。慣習で第一引数を普段使うselfではなく、clsと書きます。

class MyClass:
    CLASS_PARAM = 100

    def __init__(self, instance_param):
        self.instance_param = instance_param

    @classmethod
    def method(cls):
        print(cls.CLASS_PARAM)
        # print(cls.instance_param) これはできない

関数内ではインスタンス変数にはアクセスできませんが、クラス変数にはアクセスができます。また、他のクラスメソッドやスタティックメソッドにもアクセス可能です。

要するにインスタンス変数に用事がない場合に使用できます。

staticmethod

スタティックメソッドです。インスタンスメソッドやクラスメソッドと異なり、指定される第一引数はなく、必要な引数を1番目から書きます。

class MyClass:
    CLASS_PARAM = 100

    def __init__(self, instance_param):
        self.instance_param = instance_param

    @classmethod
    def method(cls):
        print(cls.CLASS_PARAM)
        # print(cls.instance_param) これはできない

    @staticmethod
    def method_2(param):
        print("Static!!", param)
        # cls.method() できない
        # print(cls.CLASS_PARAM) できない
        # print(self.instance_param) できない

関数内ではインスタンス変数やクラス変数、他の関数にもアクセスできません。クラスの他の実装に依存しない関数です。

要するに独立した関数として実装できる場合に使えます。

使い方は分かった。で??

使い方は冒頭記載した通り難しくありません。それぞれの制約も割とシンプルだと思います。

ただ、おそらくほとんどの人の感想は以下の様なものでしょう。

「なるほど、メソッドにも種類があるのね。で??全部インスタンスメソッドで書けばいいんじゃない?」

実際その通りで、上記の例においても以下の様に全部インスタンスメソッドとして実装できます。

class MyClass:
    CLASS_PARAM = 100

    def __init__(self, instance_param):
        self.instance_param = instance_param

    def method(self):
        print(self.CLASS_PARAM)

    def method_2(self, param):
        print("Static!!", param)

使い分けるメリット(個人見解)

使い分けるメリットとしては以下の3つかなと考えています。

  • 可読性・保守性
  • インスタンス化の手間を省く
  • パフォーマンス

一個ずつ気持ちを書いていきます。

可読性・保守性

人によって答えの異なる分野だとは思いますが、個人的には各種メソッドを使い分けるもっとも大きなメリットだと考えています。

デコレータが出てくるし、selfだったり、clsだったり、第一引数なしだったりでややこしいから逆に可読性下がるんじゃないかといった意見もきっとあると思います。

ただ、他人のコードを読んだり、修正する機会が増えてきて感じたのは、「インスタンス変数が絡む関数は読むのが大変な傾向にある」ということです。

特に、別の関数で操作されたインスタンス変数がさらに別の関数で登場してくる様なケースは割としんどくて、一直線にコードが読めないのと、インスタンス変数への意識に脳のリソースを奪われる感じがあってとても疲れます。

個人的には読むのがしんどい = 修正もしんどいなので、保守の観点でもやはり辛いです。

やりたいことの都合上、インスタンス変数を操作せざるを得ないことはままあると思うのでそれ自体は頑張るだけなのですが、全部インスタンスメソッドとして実装されていると、この関数はどこかでインスタンス変数を操作しているのか??という割と大事な情報が、関数を全部読むまでわからないという事態になります。

関数の頭にデコレータで@staticmethodと書いてあれば、「この関数はインスタンス変数にはアクセスしないのか」とすぐにわかるので読むのも楽ですしそれはイコール修正するときも気が楽です。

長くなりましたが、つまるところ、インスタンス変数やクラス変数・関数への依存が一目見てわかるのは可読性・保守性の観点から大きなメリットではないか

というのが私の気持ちです。

インスタンス化の手間を省く

残りはおまけ程度ですが、一応記載しておきます。

2つめは割とわかりやすいメリットです。

インスタンスメソッドを使うときは一度インスタンス化しないとダメですが、クラスメソッドやスタティックメソッドであればその必要がありません。

無駄な処理をするのってすごく気持ちが悪いですし、やはりスマートでかっこいい実装ができた方がテンションもあがるので、こういう細かいところもこだわっていきたいなという気持ちです。

# インスタンスメソッドとして実装されている場合
object = MyClass()
object.method()

# クラスメソッドとして実装されている場合
MyClass.method()

パフォーマンス

最後です。これはまだ実感としてもっているわけではないのですが理論的にはという話です。

毎回インスタンス化しなくていいということは、処理も減るし、無駄なインスタンスにメモリを使わなくて良いということです。

クラスの関数をいろんなところで使う場合に、インスタンスメソッドとして実装していると10個、20個とインスタンスを作るはめになりますが、それが全部不要になります。

塵も積もればなんとやらなので、規模の大きいシステムの場合はこういった細かいところの配慮も大事になってくるのかもしれないです。

まとめ

ということでようやく謎が解けた気分なので気持ちを書いてみました。

こういう考えであることを職場の方と相談したところ、共感していただけてこれまで全部インスタンスメソッドで実装していたんですが、これからはそれぞれ使い分けて行こうぜという話になりました!

正解はないのかもしれませんが、こういうところの思想についていろんな方とお話ししてみたいものです。(ご意見待ってます)

コメント

タイトルとURLをコピーしました