Pythonでクラスを使用する必要があるのはいつですか?


175

私は約2年間pythonでプログラミングしています。主にデータ関連(pandas、mpl、numpy)ですが、自動化スクリプトや小さなWebアプリも含まれます。私はより優れたプログラマーになり、Pythonの知識を増やしたいと思っています。そして、気になったことの1つは、クラスを使用したことがないということです(小さなWebアプリのランダムフラスココードをコピーする以外に)。私は一般的にそれらが何であるかを理解していますが、なぜ単純な関数でそれらを必要とするのかについて頭を抱えているようには思えません。

私の質問に具体性を追加するには:常に複数のデータソース(mongo、sql、postgres、apis)からデータを取得し、大量または少量のデータの変更とフォーマットを実行し、csv / excelにデータを書き込む、大量の自動レポートを作成します/ html、メールで送信します。スクリプトの範囲は〜250行から〜600行です。これを行うためにクラスを使用する理由はありますか?なぜですか?


15
より適切にコードを管理できれば、クラスを持たないコードを作成しても問題はありません。OOPプログラマーは、言語設計の制約やさまざまなパターンの表面的な理解のために、問題を誇張する傾向があります。
Jason Hu

回答:


132

クラスはオブジェクト指向プログラミングの柱です。OOPは、コードの編成、再利用性、およびカプセル化に深く関わっています。

まず、免責事項:OOPは、 、Pythonでよく使用される別のパラダイムである関数型プログラミングです。Python(または確かにほとんどの言語)でプログラミングするすべての人がOOPを使用するわけではありません。Java 8では、オブジェクト指向ではない多くのことができます。OOPを使用したくない場合は、使用しないでください。二度と使用しないデータを処理するための1回限りのスクリプトを作成しているだけの場合は、現在の方法を続けてください。

ただし、OOPを使用する理由はたくさんあります。

いくつかの理由:

  • 組織:OOPは、データとプロシージャの両方をコードで記述および定義する、よく知られた標準的な方法を定義します。データとプロシージャの両方を(異なるクラスの)定義のさまざまなレベルで格納でき、これらの定義について話す標準的な方法があります。つまり、OOPを標準的な方法で使用すると、後で自分や他の人がコードを理解、編集、使用するのに役立ちます。また、複雑な任意のデータストレージメカニズム(dictのdictまたはリスト、dictのセットまたはdictのセットのリストなど)を使用する代わりに、データ構造の一部に名前を付けて、それらを簡単に参照できます。

  • 状態:OOPは、状態を定義して追跡するのに役立ちます。たとえば、古典的な例では、学生を処理するプログラム(たとえば、学年プログラム)を作成している場合、それらについて必要なすべての情報(名前、年齢、性別、学年、コース、成績、教師、仲間、食事療法、特別なニーズなど)。このデータは、オブジェクトが生きている限り保持され、簡単にアクセスできます。

  • カプセル化:カプセル化では、手順とデータが一緒に保存されます。メソッド(関数のOOP用語)は、メソッドが操作および生成するデータと一緒に定義されます。アクセス制御を可能にする Javaなどの言語、またはPythonでは、パブリックAPIの記述方法に応じて、メソッドとデータをユーザーから隠すことができます。つまり、コードを変更する必要がある場合、またはコードを変更したい場合は、コードの実装に対して何でも実行できますが、パブリックAPIは同じままにします。

  • 継承:継承を使用すると、データとプロシージャを1つの場所(1つのクラス)で定義し、後でその機能をオーバーライドまたは拡張できます。たとえば、Pythonでは、dict機能を追加するためにクラスのサブクラスを作成する人がよくいます。一般的な変更は、存在しない辞書からキーが要求されたときに例外をスローするメソッドをオーバーライドして、不明なキーに基づくデフォルト値を提供することです。これにより、自分のコードを今すぐまたは後で拡張したり、他の人が自分のコードを拡張したり、他の人のコードを拡張したりできるようになります。

  • 再利用性:これらすべての理由およびその他の理由により、コードの再利用性が向上します。オブジェクト指向のコードを使用すると、確実な(テスト済みの)コードを1回記述してから、繰り返し再利用できます。特定のユースケースに合わせて何かを調整する必要がある場合は、既存のクラスから継承して、既存の動作を上書きできます。何かを変更する必要がある場合は、既存のパブリックメソッドシグネチャを維持しながらすべてを変更できます。

繰り返しますが、OOPを使用しない理由はいくつかありますが、使用する必要はありません。しかし、幸運なことに、Pythonのような言語では、少しでも多くでも使用できます。

学生の使用例(コードの品質は保証されず、例にすぎません):

オブジェクト指向

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

標準辞書

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

"yield"のため、Pythonのカプセル化は多くの場合、クラスよりもジェネレータやコンテキストマネージャの方がきれいです。
ドミトリー・ルバノビッチ2015年

4
@meter例を追加しました。お役に立てば幸いです。ここでの注意は、正しい名前の辞書のキーに依存する必要がある代わりに、Pythonインタープリターは、混乱して定義済みのメソッドを使用するように強制した場合に、この制約を作成することです(ただし、定義されたフィールドではありません(Javaおよびその他のOOP言語では、Pythonなどのクラス外のフィールドを定義できません))。
dantiston、2015年

5
カプセル化の例として@meterも使用します。たとえば、1学期に一度、大学の50,000人の学生のGPAを取得するだけでよいので、今日の実装は問題ないとします。明日は助成金を受け取り、毎秒すべての生徒の現在のGPAを毎秒与える必要があります(もちろん、誰もこれを要求するのではなく、単に計算的に困難にするためです)。次に、GPAを「メモ」し、それが変更されたときにのみ(たとえば、setGradeメソッドで変数を設定することによって)計算し、その他はキャッシュされたバージョンを返します。ユーザーは引き続きgetGPA()を使用しますが、実装が変更されました。
dantiston、2015年

4
@dantiston、この例にはcollections.namedtupleが必要です。新しいタイプStudent = collections.namedtuple( "Student"、 "name、age、gender、level、grades")を作成できます。次に、インスタンスjohn = Student( "John"、12、 "male"、grades = {'math':3.5}、level = 6)を作成できます。クラスの作成と同じように、位置引数と名前付き引数の両方を使用することに注意してください。これは、Pythonですでに実装されているデータ型です。次に、john [0]またはjohn.nameを参照して、タプルの最初の要素を取得できます。ジョンの成績をjohn.grades.values()として取得できるようになりました。そして、それはあなたのためにすでに行われています。
ドミトリー・ルバノビッチ

2
私にとって、カプセル化は常にOOPを使用する十分な理由です。妥当なサイズのコーディングプロジェクトでOOPを使用していないことの価値を確認するのに苦労しています。私は:)逆質問に対する答えを必要とする推測
サンジェイ

23

関数の状態を維持する必要があり、ジェネレーター(戻り値ではなく生成される関数)では達成できない場合。ジェネレーターは独自の状態を維持します。

標準のいずれかを上書きしたい場合演算子の場合は、クラスが必要です。

Visitorパターンを使用する場合は常に、クラスが必要になります。他のすべての設計パターンは、ジェネレーター、コンテキストマネージャー(クラスとしてよりもジェネレーターとして実装するほうがよい)、およびPODタイプ(辞書、リスト、タプルなど)を使用して、より効果的かつクリーンに実行できます。

「pythonic」コードを記述したい場合は、クラスよりもコンテキストマネージャとジェネレータを優先する必要があります。きれいになります。

機能を拡張したい場合は、ほとんどの場合、継承ではなく包含でそれを実現できます。

すべてのルールとして、これには例外があります。機能をすばやくカプセル化する場合(つまり、ライブラリレベルの再利用可能なコードではなくテストコードを作成する場合)、クラスに状態をカプセル化できます。それは簡単で、再利用可能である必要はありません。

C ++スタイルのデストラクタ(RIIA)が必要な場合は、クラスを使用したくありません。コンテキストマネージャが必要です。


1
@DmitryRubanovichクロージャーは、Pythonのジェネレーターを介して実装されていません。
Eli Korvigo 2018年

1
@DmitryRubanovich私が言及したのは「クロージャーはPythonでジェネレーターとして実装されている」ということですが、これは正しくありません。クロージャーははるかに柔軟です。ジェネレーターはGeneratorインスタンス(特別なイテレーター)を返すようにバインドされていますが、クロージャーは任意の署名を持つことができます。クロージャを作成することで、ほとんどの場合、基本的にクラスを回避できます。また、クロージャは単に「他の関数のコンテキストで定義された関数」ではありません。
Eli Korvigo 2018年

3
@Eli Korvigo、実際には、ジェネレーターは構文的に大きな飛躍です。これらは、関数がスタックの抽象化であるのと同じ方法で、キューの抽象化を作成します。そして、ほとんどのデータフローはスタック/キュープリミティブからつなぎ合わせることができます。
ドミトリールバノビッチ

1
@DmitryRubanovich私たちはここでリンゴとオレンジを話している。私が言っているのは、ジェネレーターは非常に限られた数のケースで有用であり、汎用のステートフルな呼び出し可能オブジェクトの代替と見なすことはできません。あなたは私の意見に矛盾することなく、彼らがどれほど素晴らしいかを私に伝えています。
Eli Korvigo 2018年

1
@Eli Korvigo、そして呼び出し可能オブジェクトは関数の一般化にすぎないと言っています。それ自体がスタックの処理よりも構文上の砂糖です。一方、ジェネレーターはキューの処理よりも構文上の砂糖です。しかし、構文のこの改善により、より複雑な構成を簡単に、より明確な構文で構築できます。'.next()'はほとんど使用されません。
ドミトリールバノビッチ

11

あなたはそれを正しく行うと思います。いくつかのビジネスロジックや、難しい関係を持つ難しい現実のプロセスをシミュレートする必要がある場合、クラスは妥当です。例として:

  • 共有状態を持ついくつかの関数
  • 同じ状態変数の複数のコピー
  • 既存の機能の動作を拡張するには

また、このクラシックビデオをご覧になることをお勧めします


3
Pythonでコールバック関数が永続的な状態を必要とする場合、クラスを使用する必要はありません。returnの代わりにPythonのyieldを使用すると、関数が再入可能になります。
ドミトリールバノビッチ

4

クラスは実世界のエンティティを定義します。個別に存在し、他から分離された独自のロジックを持つ何かに取り組んでいる場合は、そのためのクラスを作成する必要があります。たとえば、データベース接続をカプセル化するクラスです。

そうでない場合は、クラスを作成する必要はありません


0

それはあなたのアイデアとデザインに依存します。あなたが優れたデザイナーであれば、OOPはさまざまなデザインパターンの形で自然に出てきます。単純なスクリプトレベルの処理では、OOPがオーバーヘッドになる可能性があります。単純に、再利用可能や拡張可能などのOOPの基本的な利点を検討し、それらが必要かどうかを確認します。OOPは複雑なものをより単純にし、より単純なものを複雑にします。OOPを使用するかしないかのどちらかの方法で物事を単純に保つだけです。どちらがより簡単なのでしょうか。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.