Pythonでクラスを設計するにはどうすればよいですか?


143

私は以前の質問に対して本当に素晴らしい助けをしてきました 足内の足の指を検出するが、これらのソリューションはすべて、一度に1つの測定でしか機能しません。

今私はオフで構成されるデータを持っています

  • 約30匹の犬;
  • それぞれに24の測定値があります(いくつかのサブグループに分割されます)。
  • 各測定には少なくとも4つの接点(各足に1つ)があり、
    • 各接点は5つの部分に分かれており、
    • 接触時間、位置、総力などのいくつかのパラメータがあります。

代替テキスト

明らかに、すべてを1つの大きなオブジェクトに固執してもそれを切り取ることはできないため、現在の大量の関数の代わりにクラスを使用する必要があると考えました。しかし、クラスについてのPythonの学習の章を読みましたが、自分のコード(GitHubリンク)には適用できません。

すべてのデータを毎回処理するのは奇妙だと思う、情報を取得するています。各足の位置がわかったら、これを再度計算する理由はありません。さらに、同じ犬のすべての足を比較して、どの接触がどの足に属するか(前/後ろ、左/右)を判別したいと思います。機能だけを使い続けると、これはめちゃくちゃになります。

それで、私は私のデータ(1匹の犬の圧縮されたデータへのリンク)を賢明な方法で処理できるようにするクラスを作成する方法についてのアドバイスを探しています。


4
データベースの使用を検討することもできます(sqlite:docs.python.org/library/sqlite3.htmlなど)。巨大なデータファイルを読み取り、データベーステーブルの行に変換するプログラムを作成できます。次に、第2段階として、データベースからデータを引き出してさらに分析を行うプログラムを作成できます。
unutbu 2010年

ここで @ubutbu に尋ねたような意味ですか?そうすることを計画していますが、最初にすべてのデータをより整理された方法で処理できるようにしたいと思います
Ivo Flipse

回答:


434

クラスの設計方法。

  1. 言葉を書き留めます。あなたはこれを始めました。一部の人々は、なぜ彼らは問題を抱えていないのか疑問に思っています。

  2. これらのオブジェクトが何をするかについての単語のセットを単純なステートメントに拡張します。つまり、これらのことについて行うさまざまな計算を書き留めます。30匹の犬、24匹の測定値、4匹の接触、および接触ごとのいくつかの「パラメータ」の短いリストは興味深いですが、ストーリーの一部にすぎません。「各足の位置」と「同じ犬のすべての足を比較して、どの接触がどの足に属するかを判別する」は、オブジェクト設計の次のステップです。

  3. 名詞に下線を引きます。真剣に。一部の人々はこれの価値について議論しますが、私は初めてのOO開発者にとってそれが役立つと思います。名詞に下線を引きます。

  4. 名詞を確認します。「パラメータ」や「測定値」などの総称名は、問題ドメインの問題に当てはまる具体的で具体的な名詞に置き換える必要があります。詳細は、問題を明確にするのに役立ちます。ジェネリックスは単に詳細を省略します。

  5. 各名詞(「連絡先」、「足」、「犬」など)について、その名詞の属性とそのオブジェクトが関与するアクションを書き留めます。これをショートカットしないでください。すべての属性。たとえば、「データセットには30匹の犬が含まれています」が重要です。

  6. 各属性について、これが定義済みの名詞との関係であるか、それとも文字列や浮動小数点数などの「プリミティブ」または「アトミック」データであるか、または還元できないものかを特定します。

  7. アクションまたは操作ごとに、どの名詞が責任を持ち、どの名詞が単に参加するかを識別する必要があります。それは「可変性」の問題です。一部のオブジェクトは更新されますが、他のオブジェクトは更新されません。ミュータブルなオブジェクトは、それらのミューテーションに対して全責任を負う必要があります。

  8. この時点で、名詞をクラス定義に変換し始めることができます。一部の集合名詞はリスト、辞書、タプル、セット、または名前付きタプルであり、それほど多くの作業を行う必要はありません。他のクラスは、派生データが複雑であるか、実行される更新/変更が原因で、より複雑です。

unittestを使用して、各クラスを個別にテストすることを忘れないでください。

また、クラスは変更可能でなければならないという法律はありません。たとえば、あなたの場合、変更可能なデータはほとんどありません。持っているのは、ソースデータセットから変換関数によって作成された派生データです。


24

以下のアドバイス(@ S.Lottのアドバイスと同様)は、「Beginning Python:初心者からプロフェッショナルまで」という本からの抜粋です。

  1. 問題の説明を書き留めます(問題は何をすべきですか?)。すべての名詞、動詞、形容詞に下線を引きます。

  2. 名詞を調べ、潜在的なクラスを探します。

  3. 動詞を調べ、潜在的な方法を探します。

  4. 形容詞を調べ、潜在的な属性を探す

  5. クラスにメソッドと属性を割り当てる

クラスを洗練するために、本は私たちが次のことをすることができることも助言します:

  1. 一連のユースケース(プログラムの使用方法のシナリオ)を書き留めます(または考え出します)。すべてを機能的にカバーするようにしてください。

  2. すべてのユースケースを段階的に検討し、必要なものがすべてカバーされていることを確認します。


私たちが書くことになっている種類の文のいくつかの例があるとよいでしょう。
エンドリス

14

私はTDDアプローチが好きです...それでは、動作をどのようにしたいかをテストすることから始めます。合格するコードを記述します。この時点では、設計についてあまり心配する必要はありません。合格したテストスイートとソフトウェアを入手してください。複雑なメソッドを持つ、1つの大きな醜いクラスになってしまったとしても心配しないでください。

場合によっては、この初期プロセス中に、テストしにくいためにテストが難しく、分解する必要がある動作を見つけることがあります。これは、別のクラスが保証されていることのヒントになる場合があります。

次に、楽しい部分...リファクタリング。作業用ソフトウェアを入手したら、複雑な部分を確認できます。多くの場合、動作の小さなポケットが明らかになり、新しいクラスを示唆しますが、そうでない場合は、コードを単純化する方法を探してください。サービスオブジェクトと値オブジェクトを抽出します。メソッドを簡略化します。

gitを適切に使用している場合(gitを使用していますよね?)、リファクタリング中に特定の分解を非常にすばやく実験し、それを放棄して、物事を単純化しない場合は元に戻すことができます。

最初にテスト済みの動作するコードを作成することで、設計優先のアプローチでは簡単に得ることができなかった問題の領域を詳しく知ることができます。テストとコードを書くと、「どこから始めればよいか」という麻痺を乗り越えられます。


1
私もこの回答に同意します。ただし、問題を分解し、可能なクラスを特定する(つまり、「十分な」ソフトウェアアーキテクチャを実行する)と、複数のチームメンバーが並行して問題に取り組む場合に非常に役立ちます。
Ben Smith

3

OO設計の全体的なアイデアは、コードを問題にマップすることです。たとえば、犬の最初の足音が必要な場合は、次のようにします。

dog.footstep(0)

さて、あなたのケースでは、生データファイルを読み込んで足跡の場所を計算する必要があるかもしれません。これはすべて、footstep()関数で非表示にして、一度だけ発生するようにすることができます。何かのようなもの:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[現在、これは一種のキャッシングパターンです。最初に行って足跡データを読み取り、それ以降はself._footstepsから取得するだけです。]

しかし、はい、オブジェクト指向の設計を正しく行うことは難しい場合があります。データに対して実行したいことをもっと考えてください。そうすれば、どのクラスにどのメソッドを適用する必要があるかがわかります。


2

名詞、動詞、形容詞を書き出すことは素晴らしいアプローチですが、どのデータを非表示にするべきかという質問をするようにクラス設計を考えるのが好きです。です。

あなたがQueryオブジェクトとDatabaseオブジェクトを:

このQueryオブジェクトは、クエリの作成と保存に役立ちます。ここでは、保存が重要です。関数を使用すると、クエリを簡単に作成できます。多分あなたはとどまることができます:Query().select('Country').from_table('User').where('Country == "Brazil"')。構文は厳密には関係ありません-それがあなたの仕事です!-重要なのは、オブジェクトが何か隠すのに役立つことです。この場合、クエリの保存と出力に必要なデータです。オブジェクトの威力は、それを使用する構文(この場合は巧妙な連鎖)にあり、オブジェクトを機能させるために何が格納されているかを知る必要がないことにあります。正しく行われた場合Queryオブジェクトは複数のデータベースのクエリを出力できます。内部的には特定の形式を格納しますが、出力時に他の形式(Postgres、MySQL、MongoDB)に簡単に変換できます。

次にDatabaseオブジェクトについて考えてみましょう。これは何を隠して保存しますか?データベースがあるので、データベースの完全なコンテンツを保存することはできません。だからポイントは何ですか?目標は、オブジェクトを使用するユーザーからデータベースの動作隠すDatabaseことです。良いクラスは、内部状態を操作するときの推論を単純化します。このためDatabaseオブジェクトでは、ネットワーク呼び出しの動作を非表示にしたり、クエリや更新をバッチ処理したり、キャッシュレイヤーを提供したりできます。

問題はこれです Databaseオブジェクトが巨大であることです。これはデータベースへのアクセス方法を表すため、内部では何でもすべてを実行できます。システムによっては、ネットワーキング、キャッシング、およびバッチ処理を処理するのが非常に難しいので、それらを非表示にすると非常に役立ちます。しかし、多くの人々が気づくように、データベースはめちゃくちゃ複雑であり、生のDB呼び出しから離れれば離れるほど、パフォーマンスを調整し、物事がどのように機能するかを理解することが難しくなります。

これはOOPの基本的なトレードオフです。適切な抽象化を選択すると、コーディングが簡単になります(文字列、配列、辞書)。大きすぎる抽象化(データベース、EmailManager、NetworkingManager)を選択すると、複雑すぎて、それがどのように機能するか、または何をすべきかを理解できなくなる場合があります。期待する。目標は複雑さ隠すことですが、ある程度の複雑さが必要です。経験則としては、Managerオブジェクトを回避することから始めて、代わりに、structsデータを保持するだけのようなクラスを作成します。データを作成/操作するヘルパーメソッドを使用して、作業を簡単にします。たとえば、オブジェクトを取得するEmailManager呼び出された関数で開始sendEmailするEmail場合。これは単純な出発点であり、コードは非常に理解しやすいものです。

あなたの例として、あなたが探しているものを計算するためにどのようなデータが一緒になる必要があるかを考えてください。たとえば、動物がどこまで歩いているかを知りたい場合はAnimalStep、およびAnimalTrip(AnimalStepsのコレクション)クラスを持つことができます。各トリップにすべてのステップデータが含まれるようになったので、それについて何かを理解できるはずAnimalTrip.calculateDistance()です。


2

リンクされたコードをざっと見てみたところ、この時点ではDogクラスを設計しない方がよいようです。むしろ、パンダデータフレームを使用する必要があります。データフレームは列を持つテーブルです。あなたデータフレームのような列を持っているでしょう:dog_idcontact_partcontact_timecontact_location、などのパンダは、舞台裏でnumpyのアレイを使用し、それはあなたのために多くの便利なメソッドがあります。

  • 例: my_measurements['dog_id']=='Charly'
  • データを保存します。 my_measurements.save('filename.pickle')
  • pandas.read_csv()テキストファイルを手動で読み取る代わりにを使用することを検討してください。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.