PythonのZenは、物事を行う方法は1つだけであるべきだと述べていますが、メソッドを使用する場合とメソッドを使用する場合を決定する問題に頻繁に遭遇します。
ささいな例を見てみましょう-ChessBoardオブジェクト。すべての合法的なキングの動きをボード上で利用可能にする何らかの方法が必要だとしましょう。 ChessBoard.get_king_moves()またはget_king_moves(chess_board)を作成しますか?
ここに私が見たいくつかの関連する質問があります:
私が得た答えはほとんど決定的ではありませんでした:
なぜPython一部の機能(list.index()など)ではメソッドを使用し、その他(len(list)など)では機能を使用するのですか?
主な理由は歴史です。関数は、タイプのグループに対して汎用的な操作に使用され、メソッドをまったく持たないオブジェクト(タプルなど)でも機能することを目的としていました。 Python(map()、apply()et al)。の機能的特徴を使用するとき、オブジェクトのアモルファスコレクションに容易に適用できる関数を持つことも便利です。
実際、len()、max()、min()を組み込み関数として実装すると、実際には各タイプのメソッドとして実装するよりもコードが少なくなります。個々のケースについて口論することはできますが、それはPythonの一部であり、そのような根本的な変更を行うには遅すぎます。大規模なコード破損を回避するために、関数は残っている必要があります。
興味深いことですが、上記は実際にどの戦略を採用するかについてはあまり言及していません。
これが理由の1つです。カスタムメソッドを使用すると、開発者はgetLength()、length()、getlength()などの別のメソッド名を自由に選択できます。 Pythonは、共通の関数len()を使用できるように厳密な命名を強制します。
少し面白い。私の考えでは、関数はある意味、Pythonバージョンのインターフェースです。
最後に、 グイド自身から :
アビリティ/インターフェースについて話すと、「不正な」特別なメソッド名のいくつかについて考えさせられました。言語リファレンスでは、「クラスは、特殊な名前のメソッドを定義することにより、特殊な構文(算術演算、添え字付け、スライスなど)によって呼び出される特定の操作を実装できます。」ただし、
__len__
や__unicode__
などの特別な名前を持つこれらのメソッドはすべて、構文のサポートではなく、組み込み関数の利点のために提供されているようです。おそらく、インターフェイスベースのPythonでは、これらのメソッドはABCで定期的に名前が付けられたメソッドになるため、__len__
はになります。class container: ... def len(self): raise NotImplemented
さらに考えてみると、なぜすべての構文操作が特定の適切な通常の名前のメソッドを呼び出すだけではないのかわかりませんABC。たとえば、「
<
」は、おそらく「object.lessthan
」(または「comparable.lessthan
」)を呼び出すでしょう。そのため、別の利点はPythonこのマングルされた名前の奇妙さから離れることです。これは、HCIの改善のようです。ふむ同意するかどうかはわかりません(図:-)。
最初に説明したい「Pythonの理論的根拠」が2つあります。
まず第一に、HCIの理由でx.len()よりもlen(x)を選択しました(
def __len__()
はずっと後で来ました)。実際には、次の2つの理由が絡み合っています。どちらもHCIです。(a)一部の操作では、接頭辞表記は後置記号よりも読みやすいです-接頭辞(および中置!)演算は、数学が問題について考える数学者を助ける表記法を好む数学の長い伝統を持っています。
x*(a+b)
のような式をx*a + x*b
に書き換える簡単さと、生のOO表記法を使用して同じことを行う不器用さを比較してください。(b)
len(x)
というコードを読んだとき、何かの長さを求めていることをknowします。これにより、2つのことがわかります。結果は整数であり、引数は何らかのコンテナです。それどころか、x.len()
を読むとき、x
がインターフェイスを実装するか、標準のlen()
を持つクラスから継承する何らかのコンテナであることをすでに知っている必要があります。マッピングを実装していないクラスにget()
またはkeys()
メソッドがある場合や、ファイルではないものにwrite()
メソッドがある場合にときどき生じる混乱を目撃してください。同じことを別の言い方をすると、「len」は組み込みのoperationと見なされます。私はそれを失いたくありません。あなたがそれを意味したかどうかは確かに言うことはできませんが、「def len(self):...」は確かに通常の方法に降格したいようです。私は強く-1です。
Python説明することを約束した理論的根拠の第2ビットは、単に
special
ではなく__special__
を見るために特別な方法を選んだ理由です。多くの操作を予想していましたそのクラスは、いくつかの標準(__add__
または__getitem__
)、そうでない標準(たとえば、長い間pickleの__reduce__
がCコードでまったくサポートしていなかった)をオーバーライドしたい場合があります。これらの特別な操作で通常のメソッド名を使用したくないのは、既存のクラス、またはすべての特別なメソッドの百科事典メモリのないユーザーが作成したクラスは、実装するつもりのない操作を誤って定義する可能性があるためですIvanKrstićは、これをもっと簡潔に説明したメッセージで説明しました。---Guido van Rossum(ホームページ: http://www.python.org/~guido/ )
これについての私の理解は、特定の場合、接頭辞表記法の方が理にかなっていることです(つまり、言語の観点からDuck.quackはquack(Duck)よりも理にかなっています)。
そのような場合、私の推測では、Guidoの最初のポイントのみに基づいてget_king_movesを実装することになります。しかし、それでも、同様のプッシュメソッドとポップメソッドを使用してスタッククラスとキュークラスを実装するという、関数またはメソッドである必要がありますか? (ここで私は本当にプッシュポップインターフェイスに信号を送りたいので、関数を推測します)
TLDR:関数とメソッドのどちらを使用するかを決定するための戦略がどうあるべきかを誰かが説明できますか?
私の一般的なルールはこれです-操作はオブジェクトで実行されますか、それともオブジェクトによって実行されますか?
オブジェクトによって実行される場合は、メンバー操作である必要があります。それが他のものにも適用できる場合、またはオブジェクトに対して他の何かによって行われる場合、それは関数(またはおそらく他の何かのメンバー)でなければなりません。
プログラミングを導入する場合、車などの現実世界のオブジェクトの観点からオブジェクトを記述するのは伝統的です(ただし実装は正しくありません)。あなたはアヒルに言及しているので、それで行きましょう。
class duck:
def __init__(self):pass
def eat(self, o): pass
def crap(self) : pass
def die(self)
....
「オブジェクトは本物」という類推の文脈では、オブジェクトができることなら何でもクラスメソッドを追加するのは「正しい」です。だから、私はアヒルを殺したいと言って、.kill()をアヒルに追加しますか?いいえ...私の知る限り、動物は自殺しません。したがって、アヒルを殺したい場合は、これを行う必要があります:
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 design。
カプセル化プリンシパルは、実際にはこれに帰着するものです。設計者としては、ユーザーや他の開発者が必ずしもアクセスする必要のない実装とクラス内部に関するすべてを隠す必要があります。クラスのインスタンスを扱うため、これは「どの操作が重要かこのインスタンスで」になります。操作がインスタンス固有ではない場合、それはメンバー関数であってはなりません。
TL; DR:@Bryanが言ったこと。インスタンスで動作し、クラスインスタンスの内部にあるデータにアクセスする必要がある場合は、メンバー関数である必要があります。
次の場合にクラスを使用します。
1)呼び出しコードを実装の詳細から分離します- abstraction および encapsulation を活用します。
2)他のオブジェクトの代わりになりたい場合- ポリモーフィズム を利用します。
3)同様のオブジェクトのコードを再利用したい場合- 継承 を利用します。
多くの異なるオブジェクトタイプで意味のある呼び出しに関数を使用します。たとえば、組み込みのlenおよびrepr関数は多くの種類のオブジェクトに適用されます。
そうは言っても、選択は時々好みの問題になります。一般的な通話で最も便利で読みやすいものを考えてください。たとえば、_(x.sin()**2 + y.cos()**2).sqrt()
_とsqrt(sin(x)**2 + cos(y)**2)
のどちらが良いでしょうか?
簡単な経験則は次のとおりです。コードがオブジェクトの単一インスタンスに作用する場合は、メソッドを使用します。さらに良い方法は、関数として記述する理由がなければ、メソッドを使用することです。
特定の例では、次のようになります。
chessboard = Chessboard()
...
chessboard.get_king_moves()
考えすぎないでください。 「これをメソッドにするのは理にかなっていない」と言うポイントになるまで、常にメソッドを使用します。その場合、関数を作成できます。
私は通常、人のようなオブジェクトを考えます。
属性は、人の名前、身長、靴のサイズなどです。
メソッドおよび関数は、ユーザーが実行できる操作です。
この特定の人物に固有の何かを必要とせずに(そしてこの特定の人物に何かを変更することなく)誰でも操作できる場合、それはfunctionであり、そのような。
操作が人に作用する場合(例:食べる、歩く、...)または何かユニークなものが必要です参加するためにこの人に(ダンス、本を書くなど)、それはmethodでなければなりません。
もちろん、これを作業中の特定のオブジェクトに翻訳することは必ずしも簡単ではありませんが、それを考えるのに良い方法であると思います。
通常、クラスを使用して一部のthingの機能の論理セットを実装しているため、プログラムの残りの部分ではthingについて推論でき、すべてを心配する必要はありませんその実装を構成する小さな懸念。
「thingでできること」のコア抽象化の一部であるものは、通常、メソッドでなければなりません。通常、内部データの状態はプライベートであり、「thingでできること」の論理的概念の一部ではないため、これにはthingを変更できるすべてのものが含まれます。 。
特に複数のthingsが関係する場合、より高いレベルの操作を行う場合、通常はthingのパブリックアブストラクションから構築できる場合、関数として最も自然に表現されます。内部への特別なアクセスを必要とせず(他のオブジェクトのメソッドでない限り)。これには、インターフェイスを変更せずにthingが機能する方法の内部を完全に書き換えることを決定したときに、書き換えるメソッドの小さなコアセットがあり、その後すべての外部それらのメソッドの観点から書かれた関数は、まさに機能します。クラスXで行うすべての操作がクラスXのメソッドであると主張すると、クラスが複雑になりすぎます。
それは私が書いているコードによって異なります。一部のプログラムでは、相互作用がプログラムの動作を引き起こすオブジェクトのコレクションとしてモデル化します。ここで最も重要な機能は単一のオブジェクトに密接に結合されているため、ユーティリティ関数が散在してメソッドに実装されています。他のプログラムにとって最も重要なものは、データを操作する関数のセットであり、クラスは、関数によって操作される自然な「アヒル型」を実装するためにのみ使用されています。
「あいまいさに直面して、推測する誘惑を拒否する」と言うかもしれません。
しかし、それは推測でさえありません。両方のアプローチの結果が問題を解決するという点で同じであることは間違いありません。
目標を達成するために複数の方法があるのは良いことだと思います。他のユーザーがすでに行ったように、言語の観点から「味が良い」/「感じやすい」直観的のいずれかを採用することを謙虚に伝えたいと思います。