別のクラスをもとに作るクラス
継承を使うと、既存のクラスを拡張したものとして新しいクラスを定義できます。既存のクラスが親(基底クラス、スーパークラスとも呼びます)、新しいクラスが子(サブクラス)です。子は親のメソッドとクラス属性をすべて、書き直すことなく受け継ぎます。さらに独自のものを追加したり、違う動作にしたいものを置き換えたりできます。
親はクラス名のあとのカッコの中で宣言します。
出力:
Rex is an animal
Woof
Dog には独自の __init__ も describe もありません。Dog("Rex") を呼ぶと、Python は Dog で __init__ を探し、見つからないので Animal のものを使います。rex.describe() でも同じ探索が行われます。rex.speak() の場合は、最初に Dog で speak が見つかるので、そこで探索が止まります。
この探索順こそが仕組みのすべてです。Python はオブジェクト自身のクラス、次に親、次に親の親と探していき、最初に見つかったものを使います。クラスが初めての人は、先に クラスのページ で __init__、self、メソッドを確認してください。
メソッドのオーバーライド
親のメソッドと同じ名前のメソッドを子に定義すると、子のインスタンスではそちらが親のメソッドに取って代わります。これをオーバーライドと呼びます。サブクラスはそれぞれ、同じ呼び出しに独自の方法で応えられます。
出力:
Rectangle with area 12
Square with area 25
Shape with area 0
summary は Shape に1回だけ定義されていますが、その中で self.area() を呼んでいます。self は実際のオブジェクトなので、Rectangle なら Rectangle.area が実行され、Square は親の Rectangle で area を見つけます。親のコードは、子の存在を知らないまま子のメソッドを呼んでいるのです。共通の処理の流れを共有しつつ、細部はサブクラスごとに埋められる。これが継承の便利なところです。
super() で親を呼び出す
子が独自の __init__ を定義すると、親の __init__ は自動では実行されなくなります。子が親の初期化処理も必要とするなら、super().__init__() で呼び出します。
出力:
Ada 1050.0
super() は、現在のクラスの親からメソッドを探し始めるオブジェクトを返します。したがって super().__init__(owner, balance) は同じオブジェクトに対して Account.__init__ を実行し、そのあとには self.owner と self.balance が存在します。
この行を省くと、オブジェクトは半分しか組み立てられません。
出力:
0.05
AttributeError: 'SavingsAccount' object has no attribute 'owner'
代わりに Account.__init__(self, owner) と書くこともでき、古いコードではよく見かけます。super() のほうが好まれるのは、親の名前を繰り返さずに済み、後述する多重継承とも正しく協調するからです。
メソッドを置き換えずに拡張する
super() は __init__ に限らず、どのメソッドでも使えます。オーバーライドの中で親のメソッドを呼べば、書き直すのではなく、その前後に動作を追加できます。
出力:
started
[12:00] started
INFO [12:00] started
各階層が1つずつ要素を加え、残りを上の階層に任せています。あとで親の format が変わっても、両方の子がその変更を受け取ります。
isinstance と issubclass
サブクラスのインスタンスは、その上にあるすべてのクラスのインスタンスでもあります。isinstance(obj, cls) はこの関係を確認し、issubclass(child, parent) は2つのクラスの間の関係を確認します。
type(obj) を直接比較するより、isinstance を使いましょう。type(obj) is Animal のような確認はすべてのサブクラスを拒否するので、誰かが Dog を追加した時点で壊れます。
自分で書くクラスは、明示しなくてもすべて object を継承しています。class Animal: は class Animal(object): と同じです。__str__、__repr__、__eq__ などのデフォルトのメソッドはここから来ていて、__str__ をオーバーライドすると print() の表示が変わるのもそのためです。
出力:
(2, 3)
True
サブクラスにメソッドの実装を義務づける
親を直接使うことはなく、ひな形としてだけ使いたい場合があります。abc モジュールを使うと、そのようなクラスを抽象クラスにできます。@abstractmethod を付けたメソッドは、オーバーライドしない限りクラスをインスタンス化できません。
出力の最初の3行はエクスポートの結果です。
exporting 2 rows
1,2
3,4
最後の行は TypeError で、Exporter と実装されていない export メソッドの名前が示されます(正確な文言は Python のバージョンによって異なります)。間違いは、あとで export がようやく呼ばれたときではなく、オブジェクトを作成した時点で明らかになります。
多重継承と MRO
クラスは複数の親を並べることができ、そのすべてを継承します。
出力:
paddling flapping
swims
['Duck', 'Swimmer', 'Flyer', 'object']
両方の親が move を定義していて、Duck は Swimmer のものを受け取ります。Python はメソッド解決順序(MRO)で判断します。MRO はクラスを1列に並べたリストで、Duck.__mro__ に格納されており、属性の探索はすべてこのリストを先頭から順にたどります。リストはクラス自身から始まり、親を書いた順序のまま並べ、どのクラスも自分の親より前に置き、最後が object です。順序を入れ替えて class Duck(Flyer, Swimmer) にすると、move は "flies" を返します。
super() も同じリストに従います。Swimmer のメソッドの中の super() は「オブジェクトのクラスの MRO で Swimmer の次にあるクラス」を意味し、Duck の場合それは object ではなく Flyer です。この仕組みのおかげで、協調するクラスがそれぞれ super() を呼んでも、チェーン上のすべてのクラスがちょうど1回ずつ実行されます。2つの親が同じ祖父母を共有するひし形の継承でも同様です。
実際には、多重継承はおもに mixin に使われます。mixin は機能を1つだけ追加する小さなクラスで(to_json メソッドを追加する JsonMixin など)、メインの親1つと組み合わせて使います。完全な親を複数持つ深い階層は追いにくく、必要になることはまれです。
継承かコンポジションか
継承は「is a」の関係、つまり一方が他方の一種である関係を表します。SavingsAccount は Account の一種であり、Account を扱うコードはどれも、手を加えずに SavingsAccount でも動くはずです。この文が不自然に聞こえるなら、おそらく必要なのはコンポジションです。コンポジションは「has a」の関係、つまり一方が他方を持っている関係を表し、オブジェクトが別のオブジェクトを属性として保持し、それを呼び出します。
出力:
Roadster: 300 hp engine running
class Car(Engine) と書いても動きますが、それでは車がエンジンの一種だと主張することになり、Car がエンジンのメソッドをすべて持ち、別のクラスを作らない限り別のエンジンに交換できなくなります。コンポジションなら部品は分かれたままで、エンジンの交換は別のオブジェクトを渡すだけです。
継承が適切な道具ではないことを示すサインをいくつか挙げます。
- 子が親のメソッドのほとんどをオーバーライドしている、あるいは一部を無効にしている。
- 1つか2つのヘルパーメソッドを再利用するためだけに継承している。
- 階層が2段や3段を超えて深くなり、メソッドが何をするかを知るために複数のファイルを読まなければならない。
よくある間違い
super().__init__()を忘れる。 子の__init__が親のものを置き換えるので、親の属性が設定されず、あとでAttributeErrorが発生します。super()の呼び出しにselfを渡す。super().__init__(self, name)ではなくsuper().__init__(name)と書きます。super()はすでにメソッドを現在のオブジェクトに結び付けています。- オーバーライドでシグネチャが合っていない。 親の
speak(self)が共通のコードから呼ばれる場合、必須の引数を追加した子のspeak(self, volume)はそのコードを壊します。オーバーライドは互換性を保つか、新しい引数にデフォルト値を付けましょう。 type(obj) == SomeClassで確認する。 サブクラスが無視されます。isinstanceを使いましょう。- 無関係なクラス同士でコードを共有するために継承を使う。 共有するコードは関数やヘルパーオブジェクトに移しましょう。
よくある質問
Pythonでクラスを継承するには?
新しいクラス名のあとのカッコに親クラスを書きます:class Dog(Animal):。Dog は Animal のメソッドとクラス属性をすべて受け継ぎ、新しいものを追加したり、既存のものをオーバーライドしたりできます。
super().__init__() は何をするのですか?
親クラスの __init__ を呼び出し、親が自分の属性を設定できるようにします。サブクラスが独自の __init__ を定義すると、Python は親の __init__ を自動では呼びません。そこで、親が必要とする引数を渡して super().__init__(...) を呼び、そのあとでサブクラス独自の属性を設定します。
Pythonの isinstance と type の違いは?
isinstance(obj, Animal) は、Animal のインスタンスでも、Animal のどのサブクラスのインスタンスでも True です。type(obj) is Animal は、オブジェクトが Animal そのものから作られたときだけ True です。誰かがサブクラスを追加しても動き続けるので、isinstance を使いましょう。
Pythonは多重継承に対応していますか?
はい。class C(A, B): は両方を継承します。両方の親が同じメソッドを定義している場合、Python はメソッド解決順序(MRO)に従ってどちらかを選びます。MRO は C.__mro__ で確認できます。最初に C、次に親が左から右の順に並び、最後が object です。
継承ではなくコンポジションを使うべきなのはどんなときですか?
継承は「is a」の関係(一方が他方の一種である関係)、つまり親が期待される場所ならどこでもサブクラスを使える関係に使います(SavingsAccount は Account の一種)。コンポジション(オブジェクトを属性として持つこと)は「has a」の関係(一方が他方を持っている関係)に使います(Car は Engine を持っている)。迷ったら、あとから変更しやすいコンポジションを選びましょう。