Python 3: Deep Dive (Part 4 - OOP): 単一継承 (セクション6-1/15)
Pythonの全てのクラスは、明示的に指定しなくても自動的に組み込みの`object`クラスから継承され、これにより`init`や`repr`などの基本的なメソッドが提供されます。
単一継承は「IS-A関係」(例:四角形は形である)を表現する最も一般的で分かりやすい方法であり、`isinstance()`や`issubclass()`関数を使って継承関係を確認できます。
Pythonの継承システムはシンプルでありながら強力で、親クラスの機能を継承・拡張・オーバーライドすることができ、これはPythonの設計哲学を体現しています。
Pythonには、複数継承と単一継承の両方をサポートする強力な継承モデルがあります。複数継承では、クラスが複数の親から継承できる機能があり、興味深いが時に厄介なユースケースを生み出す可能性がありますが、単一継承に焦点を当てる方がより簡単で、はるかに一般的なシナリオです。単一継承チェーンでさえ、Pythonのすべてのクラスは最終的に組み込みの`object`クラスから継承することを覚えておくことが重要です。これは明示的に示すかどうかに関わらずです。この基礎は、インスタンス、サブクラス、そして`init`、`repr`、`eq`のような組み込みメソッドの動作を形作ります。
なぜ単一継承なのか?
継承はオブジェクト指向プログラミングの基本的な概念です:クラスを定義し、親の振る舞いと属性を再利用および拡張する子クラスを作成します。例えば、抽象的な`Shape`クラスを考えてみましょう。`Ellipse`や`Polygon`のような特殊な形状は`Shape`から継承しますが、機能を拡張またはオーバーライドします。`Polygon`の中では、`Rectangle`や`Triangle`のようなより具体的な形状を定義でき、さらに`Rectangle`は`Square`としてより特殊化できます。
この階層は「IS-A」関係を反映しています:
`Ellipse`は`Shape`である。
`Rectangle`は`Polygon`である(したがって`Shape`である)。
`Square`は`Rectangle`である。
各サブクラスは親の振る舞い(中心点を移動するメソッドなど)を継承し、機能を拡張し(例:多角形の辺の数を定義)、または親のメソッドをオーバーライドします(例:`Triangle`サブクラスで多角形の辺を正確に3つに強制する)。
サブクラス、インスタンス、階層
Pythonでサブクラスを作成する際は、以下の構文を使用します:
class Person:
pass
class Student(Person):
pass
ここで、`Student`は`Person`のサブクラスであり、`Person`を親と呼びます。同じ用語が任意の継承構造に適用されます。これらのクラスをインスタンス化できます:
p1 = Person()
s1 = Student()
`s1`は`Student`のインスタンスですが、`Student`が`Person`から継承しているため、`Person`のインスタンスとも見なされます。
ただし、`s1`は`Person`から継承する他の兄弟クラス、例えば`Teacher`のインスタンスではありません。
Pythonはこれらの階層を探索するための2つの重要な組み込み関数を提供します:
`isinstance(instance, Class)`
与えられたオブジェクトがクラスまたはその祖先のいずれかのインスタンスであるかどうかをチェックします。例えば、`isinstance(s1, Student)`と`isinstance(s1, Person)`は両方とも`True`を返します。
関連する関数である**`type(instance)`**は、インスタンスを作成した特定のクラスを返します。これは継承チェーン全体を考慮する`isinstance`とは異なります。したがって、`s1`は継承によって`Person`でもあるにもかかわらず、`type(s1)`は`Student`かもしれません。この区別は、オブジェクトの機能をチェックする方法を決める際に重要です—ほとんどの場合、`isinstance`の方が柔軟です。なぜなら、特定の振る舞い(例:`eat()`メソッド)があるかどうかを気にするのであって、必ずしもクラス名が何であるかを気にするわけではないからです。
Pythonにおける`object`の役割
明示的に`class Shape(object):`と書かなくても、PythonはどのみちShapeを`object`から継承させます。結果として、あなたが書くすべてのクラスは最終的な祖先として`object`を持ちます。カスタムクラスだけでなく、`int`、`dict`、さらにはモジュールのような組み込み型も`object`の「サブクラス」です。これは以下のようにチェックできます:
issubclass(int, object) # True
issubclass(Person, object) # True
`object`クラスは以下のようなメソッドのデフォルトバージョンを実装しています:
`init`
`repr`と`str`(おなじみの`<main.ClassName object at 0x...>`という表現を提供)
`eq`(デフォルトで`id()`の等価性をチェックするため、異なるインスタンスはメモリ内の同じオブジェクトでない限り決して等しくない)
`hash`など。
一見「空の」クラスを作成する場合:
class Person:
pass
p = Person()
`p`は`object`基底クラスのおかげで、それらの機能をすべて持っています。例えば`init`や`repr`をオーバーライドすることを選択した場合、継承されたバージョンを自分のものに置き換えます。そうしない場合、クラスは単に`object`が提供するデフォルトにフォールバックします。
簡単なコーディング例
以下のような簡単なクラス階層を定義できます:
class Shape:
pass
class Ellipse(Shape):
pass
class Circle(Ellipse):
pass
class Polygon(Shape):
pass
class Rectangle(Polygon):
pass
class Square(Rectangle):
pass
class Triangle(Polygon):
pass
関係をテストできます:
issubclass(Circle, Shape) # True
issubclass(Square, Shape) # True
issubclass(Square, Ellipse) # False
インスタンスの作成:
sq = Square()
isinstance(sq, Square) # True
isinstance(sq, Rectangle) # True
isinstance(sq, Shape) # True
すべてのパスは共有の祖先である`Shape`に、そして最終的に`object`まで遡ります。
すべてをまとめる
Pythonでは、単一継承チェーンは「is-a」関係を表現し、論理的なクラス階層を維持する明確な方法を提供します。親クラスから子クラスへと機能を継承、拡張、またはオーバーライドします。一方で、`object`基底クラスから自分でコーディングすることなく多くのデフォルトの振る舞いを得ることができます。この構造はシンプルでありながら強力であり、Pythonの設計哲学の特徴です。オブジェクト指向パターンの探索を続ける中で、単一継承が基準であり、`object`が舞台裏ですべてのクラスを静かに動かしていることを覚えておいてください。