よく設計されたクラスでコードを構造化しましょう。初期化、インスタンス属性とクラス属性、継承とそのトレードオフを学びます。
このレッスンを Kodokon で開くあなたは普段からPythonのオブジェクトを扱っています。文字列、リスト、ファイルなどです。自分でクラスを書くということは、状態(属性)と振る舞い(メソッド)を、一貫したインターフェースの背後にまとめるということです。よく誤解される用語の話をひとつ。__init__はコンストラクタではありません。__init__がselfを受け取る時点で、オブジェクトはすでに存在しています(__new__によって作られています)。その役目はインスタンスを初期化することです。そしてselfにも魔法のようなところはありません。これはすべてのインスタンスメソッドの第一パラメーターで、呼び出しのときにPythonが自動的に渡してくれます。
class Account:
def __init__(self, owner, balance=0.0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be > 0")
self.balance += amount
return self.balance
def __repr__(self):
return f"Account({self.owner!r}, {self.balance})"
account = Account("Ada")
account.deposit(120.0)
print(account)実務で決定的に重要な区別があります。クラス属性はクラスの本体で宣言され、すべてのインスタンスで共有されます。一方インスタンス属性はselfの上に、たいていは__init__の中で作られます。読み取るとき、Pythonはまずインスタンスを見て、次にクラス、さらに親クラスへとさかのぼります。self.x = ...という代入は常にインスタンス属性を作り、同じ名前のクラス属性を隠します。現場での典型的な罠は、ミュータブルなクラス属性です。
class Basket:
items = []
def add(self, item):
self.items.append(item)
a = Basket()
b = Basket()
a.add("apple")
print(b.items) # ['apple']: shared list!継承は「is a」の関係、つまり「AはBである」という関係を表します。CarはVehicleである、という具合です。サブクラスはメソッドを継承し、それを上書きでき、super()によって親のバージョンを呼び出せます。これは__init__の中では欠かせません。親の初期化を飛ばしてしまわないためです。PythonはMRO(メソッド解決順序)をたどって呼び出しを解決します。継承を機械的に選んではいけません。「is a」の関係がないままコードを再利用したいなら、コンポジション(あるオブジェクトが別のオブジェクトを持つこと)を選びましょう。深い階層はもろいものです。それぞれの親クラスが、すべての子にとって暗黙のAPIになってしまうからです。
class Vehicle:
def __init__(self, brand):
self.brand = brand
def describe(self):
return self.brand
class Car(Vehicle):
def __init__(self, brand, doors):
super().__init__(brand)
self.doors = doors
def describe(self):
base = super().describe()
return f"{base}, {self.doors} doors"
print(Car("Aster", 5).describe())