Kodokon kodokon.com

オブジェクト指向:クラス、__init__、メソッド、継承

よく設計されたクラスでコードを構造化しましょう。初期化、インスタンス属性とクラス属性、継承とそのトレードオフを学びます。

10 分 · 3 問

このレッスンを Kodokon で開く

あなたは普段からPythonのオブジェクトを扱っています。文字列、リスト、ファイルなどです。自分でクラスを書くということは、状態(属性)と振る舞い(メソッド)を、一貫したインターフェースの背後にまとめるということです。よく誤解される用語の話をひとつ。__init__はコンストラクタではありません。__init__selfを受け取る時点で、オブジェクトはすでに存在しています(__new__によって作られています)。その役目はインスタンスを初期化することです。そしてselfにも魔法のようなところはありません。これはすべてのインスタンスメソッドの第一パラメーターで、呼び出しのときにPythonが自動的に渡してくれます。

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)
完全なクラス:__init__、業務メソッド、__repr__

実務で決定的に重要な区別があります。クラス属性はクラスの本体で宣言され、すべてのインスタンスで共有されます。一方インスタンス属性selfの上に、たいていは__init__の中で作られます。読み取るとき、Pythonはまずインスタンスを見て、次にクラス、さらに親クラスへとさかのぼります。self.x = ...という代入は常にインスタンス属性を作り、同じ名前のクラス属性を隠します。現場での典型的な罠は、ミュータブルなクラス属性です。

PYTHON
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である」という関係を表します。CarVehicleである、という具合です。サブクラスはメソッドを継承し、それを上書きでき、super()によって親のバージョンを呼び出せます。これは__init__の中では欠かせません。親の初期化を飛ばしてしまわないためです。PythonはMRO(メソッド解決順序)をたどって呼び出しを解決します。継承を機械的に選んではいけません。「is a」の関係がないままコードを再利用したいなら、コンポジション(あるオブジェクトが別のオブジェクトを持つこと)を選びましょう。深い階層はもろいものです。それぞれの親クラスが、すべての子にとって暗黙のAPIになってしまうからです。

PYTHON
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())
継承とsuper():書き直さずに拡張する

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. __init__の役割は、正確には何ですか?
    • メモリ上にオブジェクトを作成する
    • すでに作成されたインスタンスを初期化する
    • クラス属性を宣言する
    • 属性にアクセスするたびに呼び出される
  2. あるクラスが本体でitems = []を宣言しています。self.items.append(1)を実行するa.add(1)のあと、別のインスタンスbには何が見えますか?
    • b.itemsは空のリスト
    • b.itemsは1を含む。リストは共有されている
    • PythonがAttributeErrorを投げる
  3. Car.__init__の中で、super().__init__(brand)という呼び出しの目的は何ですか?
    • 二つ目のVehicleインスタンスを作成する
    • 親クラスで定義された初期化を実行する
    • VehicleのメソッドをCarにコピーする
    • CarがきちんとVehicleを継承しているか確認する