検索
連載

[Python入門]クラスの継承Python入門(1/2 ページ)

Pythonにおけるクラスの役割、クラスを継承することの意味、継承の方法など、クラスの継承に関する基本知識を概観しよう。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「Python入門」のインデックス

連載目次

 前回はクラスを使って、スタックとキューと呼ばれるデータ構造を作成し、スタックを例にその機能を拡張してみた。今回はクラスの継承について見ていくことにしよう。

クラスの役割

 本連載では以前に、関数は「何らかの定型処理を行うひとまとまりのコードを再利用する」ための仕組み、パッケージやモジュールは「複数の関数などを1つ以上のファイルにまとめることで、それらを他のコードから再利用する」ための仕組みといった話をしてきた。クラスもそうした「コードの再利用を可能にする」仕組みの一つだ。

 クラスとは「何らかのデータ(インスタンス変数)と、それらを処理するためのコード(メソッド)をひとまとめにして名前を付けることで、後からそれらを再利用する」ための仕組みといえる。なお、モジュールやパッケージでクラスを定義すれば、それらももちろんインポートして利用できるようになる。

 今述べたような「何らかのデータと、それらを処理するためのコード」を1つの単位(オブジェクト)として考え、「さまざまなオブジェクトを、メソッド呼び出しを通じて、どのように作用させていくかを記述することで、目前にある問題を解決する」プログラミング方法を「オブジェクト指向プログラミング」と呼ぶ(こう書くと難しく感じるが、極端な例を挙げると、「ある文字列に含まれている文字を全て大文字にする」のが問題であれば、「文字列にupperメソッドを適用する」がその解だ)。

 Pythonでは全てのものが「オブジェクト」であり、本連載でこれまでに見てきたように、文字列やリストなどの全てのオブジェクトにはデータとそれを処理するためのメソッドが備わっている。さらに、クラスを用いて、「データとコードを備えた」型を定義できるのも前回までに見てきた通りだ。これらのことから「Pythonはオブジェクト指向プログラミングをサポートしている」と表現することもある。

オブジェクト指向プログラミングでは「データ」とそれを処理する「メソッド」がひとまとめの単位として扱われる
オブジェクト指向プログラミングでは「データ」とそれを処理する「メソッド」がひとまとめの単位として扱われる

 前回に見たスタックは、既存のリスト(listクラス)を「再利用」して、「後入れ先出し」のデータ構造を実現していた。そして、このクラスをモジュールに記述すれば、そのモジュールからスタックをいつでもインポートしてコードをさらに「再利用」できる。プログラミングとは、関数やクラス、モジュール、パッケージなど、先人たちが用意してくれたコードを、あるいは自分がこれまでに書いたコードを「再利用」しながら、自分が解決したい処理を効率よく実現する営みのことでもある。そして、今回紹介する「継承」という仕組みもまたコードの「再利用」を促進するための一つの仕組みといえる。

 Pythonにおけるオブジェクト指向プログラミングについては、回をあらためて取り上げることにして、以下ではPythonでクラスを継承する方法について見ていこう。

クラスを継承するとは

 第28回「クラスの基礎知識」の「objectクラス」で既に述べたが、Pythonでは「全てのオブジェクトの基盤」となるobjectクラスがある。以下のようにクラスを定義したときには、そのクラスはobjectクラスからさまざまな特性を受け継ぐことも述べた。

class Point:
    pass

Pointクラスはobjectクラスからさまざまな属性を受け継ぐ

 このように、何らかのクラスからその属性を受け継ぐことを、プログラミングの世界では「継承」と呼ぶ。実際には、クラスを「継承」するとは、元となるクラスの機能を受け継ぎながら、そこに別の機能を付け足していくことでもあるので、クラスを「拡張」(extend)するという見方もできる(例えば、Javaではクラスを継承するのに「extends」キーワードを使用している)。

 今述べた「元となるクラスの機能を受け継ぐ」というところが重要だ。上のような単純にクラスを定義すれば、それはobjectクラスから機能を継承する。Pythonのドキュメントによるとobjectクラスは「Python のクラスの全てのインスタンスに共通のメソッド群」を持つ。これにより、上のように定義したクラスはPythonにおける必要最低限の機能を持ったオブジェクトとして使えるようになるわけだ。

 このとき、継承元となるクラスのことを「基底クラス」「親クラス」「スーパークラス」などと呼び、継承先となるクラスのことを「派生クラス」「子クラス」「サブクラス」などと呼び、次のように図示できる。このようなクラス間の継承関係を「継承階層」と呼ぶ。継承階層においては、基底クラスが上位の階層に、派生クラスが下位の階層に位置する。

クラスの継承
クラスの継承

 また、上で示したPointクラスはobjectクラスの派生クラスであると同時に、objectクラスとしても扱える。このことから、「Pointクラスはobjectクラスである」ともいえる。このような継承関係のことを「is-a」の関係と呼ぶ(Point class is a object class)。

 リストやタプルなどの「シーケンス」と呼ばれるデータ型についても見てみよう。第25回「Pythonのオブジェクト」の「オブジェクトの型」では次のような図を示した。

シーケンス型
シーケンス型

 これらはシーケンス型という大枠に属するが、それぞれが異なる特性を持つ。

  • リスト:変更可能なシーケンス。任意の型のオブジェクトを要素にできる
  • タプル:変更不可能なシーケンス。任意の型のオブジェクトを要素にできる
  • 文字列:変更不可能なシーケンス。Unicodeのコードポイントを要素とする

 「インデックスアクセスが可能」「要素のスライスが可能」「反復可能」といったシーケンスに共通な特徴を持ちながら、要素にできるオブジェクトの種類が異なったり、変更ができたりできなかったりする。これらの継承関係を上と同様な図にすると、概念的には次のように書ける*1

シーケンス型の継承階層
シーケンス型の継承階層

*1 Pythonにおいては変更不可能なシーケンスと変更可能なシーケンスは「抽象基底クラス」と呼ばれる仕組みを使って表現されている。前者はcollections.abcモジュールのSequence抽象基底クラスで、後者はcollections.abcモジュールのMutableSequence抽象基底クラスで表される。また、これら2つの抽象基底クラスはさらにコンテナやコレクションといった概念を表すContainer抽象基底クラスやCollection抽象基底クラスなどを継承している。興味のある人は、Pythonのドキュメント「collections.abc --- コレクションの抽象基底クラス」「abstract base class」「duck-typing」などを参照されたい。


 変更可能なシーケンスが変更不可能なシーケンスを継承しているのを不思議に感じる人もいるかもしれない。つまり、以下のような図の方がよいのではないかということだ。

シーケンス型の継承階層(その2)
シーケンス型の継承階層(その2)

 だが、変更可能なシーケンスは「変更不可能なシーケンスと共通な特性」を持ち、それに対して「要素を変更するための属性を付け加えたもの」である。「共通な特性」とは「インデックスアクセス」「反復可能」「要素の存在確認」などであり、「付け加えたもの」とは「要素への代入」「要素の削除」などである。こうしたことを考えると、変更可能なシーケンスは、変更不可能なシーケンスを継承(拡張)したものだと捉えるのが適切だろう。

 ここまでの例から分かるのは次のようなことだ。

  • 基底クラス(継承階層で上位に位置するクラス)はより抽象的な(多くのクラスで共通する)概念を表現する
  • 派生クラス(継承階層で下位に位置するクラス)は基底クラスが持つ特性を継承して、それを改変したり、拡張したりする(抽象的なクラスをより具体的なクラスにしたり、特性の異なるさまざまなクラスを定義したりしていく)

 クラスを継承するということは、基底クラスで既に定義されている属性(インスタンス変数や各種メソッドなど)が新しいクラスでも適切であれば、それらを再利用して、そうでなければ独自の属性を定義していくことに他ならない。そうすることで、既に書かれたコードを何度も書く必要がなくなると共に、似た特性を持つクラス群を1つの継承階層の中にまとめることができる。

 互いに無関係なクラスに継承関係を持たせることは可能かもしれないが、そのようなことをするメリットはないのでやめておこう。継承関係を持たせてもよいのは、上で述べた「is-a」の関係が成立するときだ。

クラス継承の方法

 クラスを継承するには、単にクラス定義時にクラス名に続けて基底クラスをかっこ「()」に囲んで指定するだけだ。かっこ「()」と基底クラスを省略すると、objectクラスを基底クラスとすることは既に述べた通りだ。

class クラス名(基底クラス):
    # クラス定義の本体

クラスを継承するにはクラス名に続けて、かっこ「()」に基底クラスを指定する

 かっこ「()」内にはカンマ区切りで複数のクラスを指定してもよい。これから見るように、クラスを1つだけ指定する場合を「単一継承」と呼び、複数のクラスを指定する場合を「多重継承」と呼ぶ。多重継承については回をあらためて取り上げる。

 ここではPersonクラスと、そのクラスを継承するStudentクラスを定義してみよう。クラス名から分かる通り、Personクラスは「人」を表すクラスで、Studentクラスは人の中でも特に「学生」を表すクラスだ(「学生 is-a 人」という関係もまあ正しいといえるだろうから、この継承関係には問題はないだろう)。まずはPersonクラスの定義を以下に示す。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def hello(self):
        print('Hello,', str(self.name))
    def get_age(self):
        return self.age

Baseクラス

 ここではあえて「(object)」を付加して、Personクラスがobjectクラスを継承していることを示している。また、__init__メソッドではインスタンス変数nameにその人の名前を、もう1つのインスタンス変数ageにその人の年齢を代入している。また、「Hello ○○」と画面に表示するインスタンスメソッドと、年齢を知るためのget_ageメソッドも定義した。

 試しにPersonクラスのインスタンスを幾つか作ってみよう。

kawasaki = Person('kawasaki', 250)
isshiki = Person('isshiki', 19)
kawasaki.hello()
print(isshiki.get_age())

Personクラスの動作を確認するコード

 このコードを実行すると、次のようになる。

実行結果
実行結果

 うまく動いているようだ。次に、このクラスを継承するStudentクラスを定義する。継承時にはクラス名に続けてかっこ「(基底クラス名)」を付加するので、ここでは「(Person)」を付加する。中身についてはこの後少し考えていくので、取りあえずpass文としておく。

class Student(Person):
    pass

Personクラスを継承するStudentクラスの定義

 では、PersonクラスとStudentクラスのインスタンスを作ってみよう。

kawasaki = Person('kawasaki', 250)
isshiki = Student('isshiki', 18)
print(type(kawasaki))
print(type(isshiki))
isshiki.hello()  # 基底クラスのメソッドを呼び出す

Studentクラスの動作を確認するコード

 これを実行すると、次のように表示される。

実行結果
実行結果

 出力の最初の2行は作成したインスタンスの型を表示したものだ。それぞれPersonクラスとStudentクラスのインスタンスであることが分かる。最後の出力は、Studentクラスのインスタンスからその基底クラスであるPersonクラスのインスタンスメソッドを呼び出した結果だ。問題なく、派生クラスのインスタンスから基底クラスのメソッドが呼び出せていることが分かる。

Copyright© Digital Advantage Corp. All Rights Reserved.

       | 次のページへ
[an error occurred while processing this directive]
ページトップに戻る