Skip to content

6.14 モジュールではなくクラスとオブジェクトを使うべきなのはいつか

プログラムを構築する際、コードをクラスにまとめるべきか、モジュールにまとめるべきか迷うことがあるかもしれません。ここでは、その判断基準となるガイドラインを紹介します。

クラスとモジュールの使い分けガイドライン

Section titled “クラスとモジュールの使い分けガイドライン”
  • 複数のインスタンスが必要な場合は「クラス」: 動作(メソッド)は同じでも、内部状態(属性)がそれぞれ異なる複数のオブジェクト(インスタンス)を必要とする場合は、クラスがもっとも適しています。
  • 継承が必要な場合は「クラス」: クラスは継承をサポートしますが、モジュールはサポートしません。
  • ひとつだけ必要な場合は「モジュール」: プログラム内で唯一の存在(シングルトン)で十分な場合は、モジュールが適しています。Pythonモジュールは何度インポートされても、1つのコピーしかロードされません。
  • 複数の値と関数をまとめる場合は「クラス」: 複数の値を持つ変数を、複数の関数に引数として引き回しているような場合、それらを属性とメソッドとして1つのクラスにまとめた方がコードがすっきりします。
  • もっとも単純な方法を選ぶ: 辞書、リスト、タプルは、モジュールよりも単純で高速であり、クラスよりも普通は単純です。問題に対して過剰な構造を作り込まないようにしましょう。

Guido のアドバイス データ構造を作り込みすぎないようにしよう。オブジェクトよりもタプルの方がいい(名前付きタプルも試してみよう)。ゲッター/セッター関数よりも単純なフィールドを選ぶようにしよう。組み込みデータ型はプログラマーの友達だ。数値、文字列、タプル、リスト、集合、辞書をもっと使おう。そして、コレクションライブラリ、特にデックをチェックしよう。 — Guido van Rossum

Guidoも推奨している名前付きタプルは、タプルのサブクラスであり、位置(インデックス)だけでなく名前(.name)でも値にアクセスできる非常に便利なデータ構造です。

名前付きタプルは自動的に用意されるわけではないため、collections モジュールからインポートして使います。

namedtuple 関数に、名前と空白区切りのフィールド名文字列を渡して定義します。

from collections import namedtuple
# 'Duck'という名前の、'bill'と'tail'というフィールドを持つ名前付きタプルを定義
Duck = namedtuple('Duck', 'bill tail')
# 値を渡してオブジェクトを作成
duck = Duck('wide orange', 'long')
print(duck)
# 出力: Duck(bill='wide orange', tail='long')
# ドット記法でフィールド名を使ってアクセスできる
print(duck.bill) # wide orange
print(duck.tail) # long

辞書からの作成(キーワード引数の展開)

Section titled “辞書からの作成(キーワード引数の展開)”

辞書に格納されているデータを **(キーワード引数展開)を使って渡すことで、簡単に名前付きタプルを作成できます。

parts = {'bill': 'wide orange', 'tail': 'long'}
# 辞書のキーと値を展開して渡す
duck2 = Duck(**parts)
print(duck2)
# 出力: Duck(bill='wide orange', tail='long')

値の変更(新しいタプルの生成)

Section titled “値の変更(新しいタプルの生成)”

名前付きタプルはタプルの一種なのでイミュータブル(変更不可)ですが、_replace() メソッドを使うことで、一部のフィールドを置き換えた新しい名前付きタプルを作ることができます。

# tailとbillの値を変更した新しいオブジェクトを生成
duck3 = duck2._replace(tail='magnificent', bill='crushing')
print(duck3)
# 出力: Duck(bill='crushing', tail='magnificent')