1. ホーム
  2. python

[解決済み] Pythonで、メソッドの代わりに関数を使用するのはどんな場合ですか?

2022-08-20 10:05:35

質問

Pythonの禅では、物事を行う方法は1つだけであるべきだと述べています。しかし、私は頻繁に、いつ関数を使うべきか、いつメソッドを使うべきかという問題に遭遇します。

つまらない例ですが、ChessBoardオブジェクトを考えてみましょう。ボード上で利用可能なすべての合法的な王の動きを取得する方法が必要だとします。ChessBoard.get_king_moves()と書くか、get_king_moves(chess_board)と書くか?

以下は、私が調べた関連する質問です。

私が得た回答は、ほとんど結論が出ませんでした。

なぜ Python はある機能 (例: list.index()) にはメソッドを使い、他の機能 (例: len(list)) には関数を使うのでしょうか?

主な理由は歴史です。関数は、ある種の型のグループに対して汎用的な操作のために使用されていました。 メソッドを持たないオブジェクトでも動作するように意図されていました。 (例: タプル)。また、以下のような関数があると便利である。 また、Pythonの関数型機能を利用する際に、非定形のオブジェクトの集合に容易に適用できる関数を持つことは便利です。 Pythonの関数的な機能(map()、apply()など)を使うときに、非定形のオブジェクトの集まりに簡単に適用できる関数を持っていることも便利です。

実際、len(), max(), min() を組み込み関数として実装することは、各タイプのメソッドとして実装するよりも実際に少ないコードです。 個々のケースについてとやかく言うことはできますが、それはPythonの一部であり、また 今更そんな根本的な変更をするのは遅すぎるのです。この関数は は、大規模なコードの破損を避けるために残らなければなりません。

興味深いことではありますが、上記はどのような戦略を採用すべきかについてあまり言及していません。

<ブロッククオート

これが理由の1つです。カスタムメソッドがあれば、開発者は getLength()、length() などのように、開発者は自由に別のメソッド名を選択することができます。 getlength() などのように、開発者は自由に別のメソッド名を選ぶことができます。Pythonは厳格な命名規則を採用しているので、一般的な関数である Pythonは一般的な関数len()を使用できるように、厳密な命名を強制します。

もう少し興味深いことがあります。私の考えでは、関数はある意味、インターフェースのPythonicバージョンです。

最後に をGuido自身から :

<ブロッククオート <ブロッククオート

アビリティ/インターフェースの話をしたときに、私たちのいくつかの rogue" の特殊メソッド名について考えてみました。 言語リファレンスでは、次のように説明されています。 クラスは、特殊な構文で呼び出される特定の操作を実装することができます。 クラスは、特殊な構文で呼び出される特定の操作(算術演算や添え字、スライスなど)を、特殊な名前のメソッドを定義することで実装できます。 と書かれています。 のような特殊な名前を持つ __len__ または __unicode__ であるように見えますが は,構文をサポートするためというより,むしろ組み込み関数のために提供されているように見えます。 のために提供されているようです。 おそらく、インターフェースベースの Python では、これらの メソッドは ABC 上で通常の名前のメソッドに変わるでしょう。 __len__

class container:
  ...
  def len(self):
    raise NotImplemented

しかし、もう少し考えてみると、どうして すべて 構文 操作は、特定の ABC に対して適切な通常の名前のメソッドを呼び出すだけではありません。 を特定のABCで呼び出すだけではありません。 < 例えば、" は、おそらく次のように呼び出します。 " object.lessthan "(あるいは、" comparable.lessthan ")です。 ですから、もうひとつの もう一つの利点は、Pythonをこのマングルネームの奇妙さから引き離すことができることです。 これは HCI の改善だと思います。 .

ふむ。納得いかないですね(図星:-)。

Pythonの理論的根拠について、2つ説明したいと思います。 を説明したいと思います。

まず第一に、HCIの理由からx.len()ではなくlen(x)を選択しました( def __len__() はずっと後になりました)。実際には 2 つの絡み合った理由があり、両方とも HCI です。

(a) いくつかの操作では、prefix 記法は postfix よりも読みやすくなっています。 接頭辞 (および接尾辞!) 操作には、視覚的に役立つ表記を好む数学の長い伝統があります。 数学では長い伝統があり、ビジュアルが問題を考える数学者を助けるような記法を好みます。 数学では長い伝統があり、視覚的に問題を理解するのに役立つ記法を好みます。例えば のような式を簡単に書き換えることができます。 x*(a+b)x*a + x*b の不器用さに を、生のOO記法を用いて同じことをすることの不器用さに気づきます。

(b) 次のようなコードを読むと len(x) I 知っている それが何かを求めていることを を尋ねていることがわかります。これは2つのことを教えてくれます。 であること、そして引数はある種の容器であることです。それとは逆に を読むと x.len() を読むとき、私はすでに x はある種の コンテナはインターフェイスを実装しているか、標準的な が標準的な len() . 時折、以下のような混乱が起こることがあります。 マッピングを実装していないクラスが get() または keys() メソッドを使用したり、ファイルでないものが write() メソッドを持っています。

同じことを別の言い方で言うと、私は 'len' を組み込みの 演算 . 私はそれを失いたくない。しかし、'def len(self): ...' は確かに、あなたが普通のメソッドに降格させたいように聞こえます。 を普通のメソッドに降格させたいのだろう。それについては強く-1です。

私が説明すると約束したPythonの根拠の2つ目のビットは を見るために特別なメソッドを選択した理由です。 __special__ でなく、単に special . 私は、クラスがオーバーライドしたいであろう多くの操作を予期していました。 をオーバーライドすることを想定していたのですが、 いくつかの標準的な(例えば __add__ または __getitem__ など)、いくつかはあまり 標準的なもの(例えば、pickleの __reduce__ は長い間C言語ではサポートされていませんでした。 コードでは全くサポートされていませんでした)。私はこれらの特殊な操作に普通の なぜなら、既存のクラスや、すべての特殊なメソッドについて百科事典のように記憶していないユーザーが書いたクラスが、そのメソッド名を使用することになるからです。 なぜなら、既存のクラスや、すべての特殊なメソッドについて百科事典のように記憶していないユーザーが書いたクラスは、誤って自分たちが使う操作を定義してしまう可能性があるからです。 というのも、既存のクラスや、特殊なメソッドについて百科事典的に記憶していないユーザーが書いたクラスは、意図していない操作を誤って定義してしまう可能性があるからです。 というのも、既存のクラスや、特殊なメソッドをすべて記憶していないユーザーが書いたクラスが、意図しない操作を誤って定義してしまい、悲惨な結果になる可能性があるからです。Ivan Krstić は、私がこの文章を書き上げた後に届いたメッセージの中で、このことをより簡潔に説明している。 メッセージでより簡潔に説明しています。

-- --Guido van Rossum(ホームページ。 http://www.python.org/~guido/ )

私の理解では、特定のケースでは、接頭辞表記がより意味をなすだけであり (たとえば、言語学的な観点から、Duck.quack は quack(Duck) よりも意味をなす。)、また、関数は "interface"を許容するものである。

そのような場合、私の推測では、Guido の最初のポイントのみに基づいて get_king_moves を実装することでしょう。しかし、たとえば、同様のプッシュおよびポップ メソッドを持つスタックおよびキュー クラスを実装する場合、それらは関数またはメソッドであるべきなのか、という多くの未解決の問題が残ります。これらは関数であるべきか、それともメソッドであるべきなのでしょうか (ここでは、私は関数を推測します。なぜなら、私は本当にプッシュ・ポップ・インターフェースを知らせたいからです)。

TLDR: 関数とメソッドのどちらを使用するかを決定するための戦略を説明できる人はいますか?

どのように解決するのですか?

私の一般的なルールは、次のとおりです。 は、オブジェクトに対して、またはオブジェクトによって実行される操作ですか?

もしそれがオブジェクトによって行われるのであれば、それはメンバー操作であるべきです。もしそれが他のものにも適用できる、または何か他のものによってオブジェクトに行われるのであれば、それは関数(またはおそらく他のもののメンバ)であるべきでしょう。

プログラミングを紹介するとき、自動車などの現実世界のオブジェクトの観点からオブジェクトを説明するのは伝統的なことです(実装上は間違っていますが)。あなたはアヒルに言及しているので、それで行きましょう。

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

オブジェクトは実在する」というアナロジーの文脈では、オブジェクトができることすべてに対してクラスメソッドを追加することは正しいことです。たとえば、アヒルを殺したい場合、アヒルに を追加すればいいのでしょうか?いいえ、私の知る限り、動物は自殺をしません。ですから、もし私がアヒルを殺したいのであれば、このようにすべきです。

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."

この例えから離れると、なぜ私たちはメソッドやクラスを使うのでしょうか?それは、データを格納し、将来的に再利用や拡張ができるような方法でコードを構成したいからです。これは、OO デザインにとって非常に重要であるカプセル化の概念に私たちをもたらします。

設計者として、ユーザーや他の開発者が絶対にアクセスする必要のない実装やクラス内部に関することはすべて隠すべきです」というのが、カプセル化の原則です。私たちはクラスのインスタンスを扱うので、これは「どの操作が重要か」ということになります。 この例では となります。もし操作がインスタンス固有でないなら、それはメンバー関数であってはなりません。

TL;DR : Bryanが言った通り。もしインスタンス上で動作し、クラスインスタンスの内部データにアクセスする必要があるなら、それはメンバー関数であるべきです。