質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

【Python】 クラスメソッドとインスタンスメソッドとスタティックメソッドの使い分けについて

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,304

score 2237

いつもお世話になっております。

早速本題に入らせていただきます。

現在、PythonとDjangoを使用してWebアプリケーションを制作しているのですが、
クラスメソッドとインスタンスメソッドの使い分け、役割について理解が追い付いておりません。

下記に現在のコードを単純化したものを記載します。


class TestCommon:
    """  共通クラス """
    def method1(self):
        # self.request.POST などを使った処理
        data = TestCommon.method2(data)

    def method2(self, data):
        # method1 から呼ばれる、dataを変換する処理
        return data

class MainView(TemplateView, TestCommon):
    """ 共通クラスを継承した子クラス """
    def get(self, request, *args, **kwargs):
        # 親クラスの処理を呼ぶ
        MainView.method1()

参考にしたサイト


疑問1:各メソッドの使い分けがわからない

参考サイトには下記のように書かれています。

【インスタンスメソッド】
→ このメソッドは、インスタンスに紐づき、「self」を第一引数にとります。

【スタティックメソッド】
→ このメソッドは毎回同じ結果を出力したいときに使います

【クラスメソッド】
→ インスタンスメソッドが各インスタンスに結びつくのに対して、classmethodはクラスに結びつきます。メソッドを呼び出した時に第一引数には、クラスが自動的に代入されます。

私のイメージ的には、  method2()  はスタティックメソッドになるのではと思っています。(変換処理だけだし・・・)
method1() はただなんとなく、インスタンスメソッドか、クラスメソッドになる気がしています。あくまでもselfを使いたい=インスタンスメソッド?という浅はかな考えによるものですが・・・・。

此方について認識があっているか(多分間違っているのでしょうけれども)教えて頂きたいです。


疑問2:インスタンス化のタイミングがわからない

Python はとりあえず クラス名.メソッド名() と記載してしまえば、メソッドが使えてしまうイメージがあります。

例えば  MainView.method1()   や  TestCommon.method1()  といった感じです。

いつ  test = TestCommon()  のようにインスタンス化する必要があって、必要がない時はいつなのか全くといっていいほどイメージが付きません。

クラスメソッドを呼ぶときでしょうか、インスタンスメソッドを呼ぶときでしょうか?

それとももうとっくにインスタンス化されているから必要がないのでしょうか・・・?


長々と書いてしまいましたが、恐らく cls や self を持ったメソッドを作る利点がわかっていないのだと思います。

最近ではまわりまわってもう全部 スタティックメソッド でいいのではとすら思ってきてしまいました・・・。

・・・そんなわけはないと思うので、大変恐縮ではありますが
お時間ある方でメソッドに関する知識をお持ちの方いらっしゃいましたら
ご助力頂けますと幸いです・・・。


追記

色々Googleを漁り、家にあったPython入門の参考書のオブジェクト指向のページを端から端まで読んで
さらにこんなわかりにくい質問に回答して頂いたお二方の回答も読んだうえで、まだ理解できないぽんこつなので追記で質問させていただきます。

もし、インスタンスを参照せずにクラスだけ参照する場合は、@classmethod を
もし、インスタンスもクラスも参照しない場合は、@staticmethod を使い、

多分私が理解できていないのはこの クラスだけ参照する場合 ってどういうことなの・・・っていう根本的なところが分かっていないせいかなと思います。

下記に私のイメージを書いてみようと思います。

class TestCommon:
    test = "てすと"
  
  @classmethod
    def method1(cls):
        """ 予想:クラスメソッド (クラスのみの参照?) """
        # メンバ変数を参照=クラスだけ参照?
        print(cls.test)

    def method2(self):
        """ 予想:インスタンスメソッド(クラスとインスタンス両方参照?) """
        # selfを使わずにクラス名.メンバ変数で呼び出し?
        print(TestCommon.test)
        # インスタンスを参照?
        print(self.request.POST)

    def method3(self):
        """ 予想:インスタンスメソッド(クラス参照なし?) """
        print(self.request.POST)

    @staticmethod
    def method4(data):
        """ 予想:スタティックメソッドにすると引数エラー? """
        # 引数取らない=受け取れない???
        return data

今の私の認識は上記のような感じになっているのですが、これは合っていますでしょうか・・・

お時間あるときで構いませんので、ご教示いただけますと幸いです・・・。


教えて頂いたこの↓サイトものすごく、ものすごくわかりやすいかもしれない・・・ぽんこつにもわかる・・・
classmethod と staticmethod ってなに?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/11/28 22:11

    Python詳しいわけではないのでこちらに書きますが、読む限りではJavaやPHPとそこまで違うようには感じませんでした。
    ※Python詳しい人の解説は聴いてみたいですが

    キャンセル

  • azuapricot

    2019/11/28 23:42

    (どれも詳しくなる前に色々な現場を転々としたせいで色々ごちゃまぜになってたり知識が浅すぎるのです。。。)

    キャンセル

回答 3

checkベストアンサー

+5

Python3系を前提に回答します。2以前は仕様が違います。

直接の回答
  • インスタンスメソッド
    インスタンスの属性を触りたいときに使います。

  • クラスメソッド
    クラスの属性を触りたいときに使います。

  • スタティックメソッド
    別にメソッドにしなくてもいいのだけど(ただのトップレベル関数でOK)、「この関数は意味論的にクラスに属していた方が自然」と思ったら使う。

selfclsはそれを介してインスタンスやクラスの属性を触りたいがために受け取ります。

脇道

Python はとりあえず クラス名.メソッド名() と記載してしまえば、メソッドが使えてしまうイメージがあります。
例えば  MainView.method1()   や  TestCommon.method1()  といった感じです。

試せばわかることですが、通常のインスタンスメソッドでは無理です。きっとTypeError: ***() missing 1 required positional argument: 'self'とか出てくることでしょう。

ここで「MainView.method1なんてねーよ」というエラーにならないのは、MainViewというインスタンス(クラスオブジェクト(典型的にはtypeクラスのインスタンス))の属性にはmethod1というものは確かに存在しているからです。ただし、これはクラスの中で定義された関数そのままの関数オブジェクトです。メソッドではありませんし、通常はこれを使うことはありません(使えなくもありませんが)。

インスタンス化してMainView().method1とアクセスした場合は上とはまったく別のオブジェクトが得られます。ざっくりいえば、インスタンス自身を引き取ってselfに渡すようにしたwrapperとかクロージャのようなオブジェクト(=インスタンスメソッドオブジェクト。そのまんまmethodという型です)が返るとみなして良いでしょう。ちなみに、この「メソッドオブジェクト」は参照するたびに新たに生成されます。

参考

言語リファレンスのデータモデルと実行モデルは目を通しておいても損はないと思います。

Python 言語リファレンス — Python 3.8.0 ドキュメント

公式ではないですが、こちらのページも参考になります。わかりやすいので仕組みが気になるなら読んでください。
(と思って貼り付けたら、先にこのサイトの著者のnico25さん回答されてしまった・・・
Python の関数とメソッドの違いってなに? | Mastering Python

あとここも見ておくべきです。
なぜメソッドの定義や呼び出しにおいて 'self' を明示しなければならないのですか? | デザインと歴史 FAQ — Python 3.8.0 ドキュメント

追記に関して
## 私が付けたコメントはこのように二重の#で示すことにします

class TestCommon:
    ## このtestは他の言語で言うところのクラス変数であり、
    ## インスタンス変数は同様には作れません
    ## (__init__でselfの属性に対して代入して作ったりするが、それは特別な操作ではない
    ##  (__init__以外の場所でもできる))
    test = "てすと"

    ## これは実際こんな感じ
  @classmethod
    def method1(cls):
        """ 予想:クラスメソッド (クラスのみの参照?) """
        # メンバ変数を参照=クラスだけ参照?
        print(cls.test)


    ## TestCommon.testはいかにも駄目な感じがします
    ## 参照だけならself.testでも同じ結果が得られます。ただしこれに代入すると、
    ## self.test(インスタンス変数)がクラス変数とは別に作られてしまいます
    ## インスタンスメソッドからクラス変数に代入する場合のやり方は悩ましい問題です
    ## (これ!という答えは見たことないかも。self.__class__.testかtype(self).testの二択になり、後者のほうがどちらかというと良いということになりそうですが)
    ## https://qiita.com/FGtatsuro/items/be22c520e62a1d100e09
    ## https://stackoverflow.com/questions/25577578/access-class-variable-from-instance 
    ## https://python.ms/attribute/#_2-%E3%82%AF%E3%83%A9%E3%82%B9%E5%A4%89%E6%95%B0%E3%81%AE%E5%8F%82%E7%85%A7%E3%81%AE%E4%BB%95%E6%96%B9
    def method2(self):
        """ 予想:インスタンスメソッド(クラスとインスタンス両方参照?) """
        # selfを使わずにクラス名.メンバ変数で呼び出し?
        print(TestCommon.test)
        # インスタンスを参照?
        print(self.request.POST)


    ## これは実際こう
    def method3(self):
        """ 予想:インスタンスメソッド(クラス参照なし?) """
        print(self.request.POST)


    ## selfやclsに類するものを取らないだけで、引数自体は何ら問題なく受けとれます
    ## インスタンスメソッドのときの第二引数以降は問題なく与えられるということです
    ## 私の書き方が悪かったかも
    @staticmethod
    def method4(data):
        """ 予想:スタティックメソッドにすると引数エラー? """
        # 引数取らない=受け取れない???
        return data

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/29 00:20

    夜分遅くまでお付き合い頂き本当にありがとうございます・・・
    最終的な理解としては↓のような感じであってますでしょうか・・・?
    【 インスタンスの参照が必要 かつ、 クラス変数の参照が必要な場合(method2) 】
    ●インスタンスメソッドにする
    ●クラス変数は type(self).test か self.__class__.test で参照する

    【 クラスの参照も、インスタンスの参照も必要がない場合(method4) 】
    ●self を受け取れないだけで 引数を受け取り、普通に関数として使用することが可能
    ●staticmethod として記載することで、トップレベル関数よりも使用箇所が明確になり可読性があがる

    キャンセル

  • 2019/11/29 00:26

    それでいいと思います。

    キャンセル

  • 2019/11/29 00:30

    本当に夜遅くまでありがとうございました、ご教示頂いたことを元に学習を進めていこうと思います・・・!

    キャンセル

+2

疑問1:各メソッドの使い分けがわからない

method2 は、インスタンスを参照していないので、スタティックメソッドで正しいと思います。

私のイメージ的には、  method2() 
はスタティックメソッドになるのではと思っています。(変換処理だけだし・・・)

method1 は、インスタンスを参照する処理なので、インスタンスメソッドで正しいと思います。

 method1  はただなんとなく、インスタンスメソッドか、クラスメソッドになる気がしています。
あくまでもselfを使いたい=インスタンスメソッド?という浅はかな考えによるものですが・・・・。

もし、インスタンスを参照せずにクラスだけ参照する場合は、@classmethod を
もし、インスタンスもクラスも参照しない場合は、@staticmethod を使い、
機能に制限をかけるといいと思います。

「継承より合成」、「依存性逆転の原則」などの言葉が表す様に、
使わないオブジェクトあるいはメソッドは、
なるべく渡さない様にするというのが、流行りの様な気がします。

疑問2:インスタンス化のタイミングがわからない

無理してクラスを使う必要は、無いのかなと思っています。

クラスメソッドを呼ぶときでしょうか、インスタンスメソッドを呼ぶときでしょうか?
それとももうとっくにインスタンス化されているから必要がないのでしょうか・・・?
長々と書いてしまいましたが、
恐らく cls や self を持ったメソッドを作る利点がわかっていないのだと思います。
最近ではまわりまわって
もう全部 スタティックメソッド でいいのではとすら思ってきてしまいました・・・。

昔 static おじさんという人がいました。
みんな気味悪がっていました笑 恐らく正しいことを言っているんだろうけど、
なんかよく分からないみたいな雰囲気だったような気がします。

いまは関数型言語が注目され static おじさんの流れがあるような気がします。
以下は Ariadne という Python のウェブフレームワークを作っている人が書いているブログです。
正直、内容はあんまりわかっていませんが引用します。

しかし、2019 年、私はサービスを class では書いていません。
But I am not writing service with classes in 2019!
Schema-First GraphQL: The Road Less Travelled

これは JavaScript のフレームワーク React の話ですが勉強になります。

元々関数型プログラミング由来の React では、
class コンテナのような状態の塊は嫌われる。
class 作った瞬間に state 以外の暗黙の状態を作られることを、
Reactをよく理解した上級者は嫌ってる。
副作用を起こす手続きを限定することでコードの見通しをよくしたい、のが hooks の動機
https://twitter.com/mizchi/status/1121666161681108992

Effective Python 「項目 22:辞書やタプルで記録管理するよりもヘルパークラスを使う」では、
tuple, dict で書くのが面倒になったら class で使うといいよ、
という記述がありました。

無理してクラスを使う必要は、無いのかなと思っています。

拡張性が... とか気になるところもありますが、YAGNI と KISS の精神で、
必要のないところで大きな機能を使うのは避けた方が良いのかなと...
思ったり思わなかったりします。

では、いざクラスを書く段階になって、
各関数をどのクラスに所属させるか?という疑問があります。
それは副作用を与えるオブジェクトのクラスに所属させるのが個人的には良いような気がしています。


引用いただき、光栄です笑

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/28 23:50

    Googleで閲覧していた記事の著者様に回答して頂けるなんて恐悦至極ですね・・・。

    「classmethod と staticmethod ってなに?」の参考サイトはまだ発見できていませんでしたが、
    これものすごい分かりやすい気がします。
    もう少し頑張って読んでみます・・・
    (これだけ丁寧に回答されても理解できないぽんこつですみません・・・)

    キャンセル

  • 2019/11/29 01:04

    閲覧いただき幸いでございます。
    実際のところ、記事の離脱率は非常に高いので、
    わかりにくい記事だと認識しております。

    hayataka さんのおっしゃる通り
    classmethod, staticmethod は重要な機能では無いので
    ご無理のない範囲であればと思います。

    キャンセル

0

同じ形式のデータを沢山作るときにクラスを使います。
電話帳アプリで扱う電話帳データは一人ひとり内容が違うデータだけど、形式は同じデータ。
いろんな値の整数や、いろんな文字列も、各クラスのインスタンスです。

クラスにはデータの処理関数を書く。
インスタンスはクラスで処理するデータ。
Pythonのデータはすべて何らかのクラスのインスタンス。

クラスに定義した関数で、処理するインスタンスが必要ならインスタンスメソッドにする。
クラスに定義した関数で、処理するクラス情報が必要ならクラスメソッドにする。
クラスに定義した関数で、クラスもインスタンスも不要ならスタティックメソッドにする。
インスタンスからクラスに定義した処理関数を呼び出すことができ、インスタンスメソッドにはインスタンスが渡り、クラスメソッドにはクラスが渡り、スタティックメソッドには何も渡らない。

>>> str.lower("ABC")
'abc'
>>> "ABC".lower()
'abc'
>>> class sample:
...    def func1(instance):
...        print("instance:", instance)
...    @classmethod
...    def func2(cls):
...        print("class:", cls)
...    @staticmethod
...    def func3():
...        print("static")
...
>>> s = sample()
>>> s.func1()
instance: <__main__.sample object at 0x6ffffd15748>
>>> s.func2()
class: <class '__main__.sample'>
>>> s.func3()
static

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る