クラス (2)継承

クラスの継承

Python ではクラスの継承に多重継承ができる。クラスを継承するには、class 文のクラス名のあとの括弧の中に継承したいクラス名を列挙すればいい。

class Foo:
    def foo(self):
        print "This is foo."

class Bar:
    def bar(self):
        print "This is bar."

class Baz(Foo, Bar):
    def baz(self):
        print "This is baz."

if __name__ == '__main__':
    baz = Baz()
    baz.foo()
    baz.bar()
    baz.baz()

BazクラスはFooクラスとBarクラスを継承していて、加えて独自のメソッドbarを持っている。
これを実行すると:

^o^ > python inheritance.py
This is foo.
This is bar.
This is baz.

FooクラスとBarクラスそれぞれのメソッドが使えることがわかる。

メソッドのオーバーライド

次に、Bazクラスでbarメソッドを書き換え(オーバーライド)てみる。

class Foo:
    def foo(self):
        print "This is foo."

class Bar:
    def bar(self):
        print "This is bar."

class Baz(Foo, Bar):
    def baz(self):
        print "This is baz."

    def bar(self):
        print "This is baz, instead bar."

if __name__ == '__main__':
    baz = Baz()
    baz.foo()
    baz.bar()
    baz.baz()

実行結果:

^o^ > python inheritance2.py
This is foo.
This is baz, instead bar.
This is baz.

barメソッドが書き換わっている。こんな風に、継承したクラス(サブクラス)で継承もとのクラス(スーパークラス)のメソッドをオーバーライドすることができる。

メソッド名の衝突と継承の順番

ところで、多重継承するとメソッド名が衝突することが考えられる。つまり2つのスーパークラスそれぞれに同じ名前のメソッドが定義されていた場合だ。ちょっと試してみよう。

class Foo:
    def hello(self):
        print "Hello, this is Foo."

class Bar:
    def hello(self):
        print "Hello, this is Bar."

class Baz(Foo, Bar):
    pass

if __name__ == '__main__':
    baz = Baz()
    baz.hello()

二つのスーパークラスFooとBarには同じ名前のhelloメソッドがあって、サブクラスBazから呼び出している。実行すると:

^o^ > python inheritance3.py
Hello, this is Foo.

Fooクラスのメソッドが実行された。これはクラスを継承するときにFooを先に書いたせい、なのか?ためしにFooとBarを逆にしてみよう。

class Foo:
    def hello(self):
        print "Hello, this is Foo."

class Bar:
    def hello(self):
        print "Hello, this is Bar."

class Baz(Bar, Foo):
    pass

if __name__ == '__main__':
    baz = Baz()
    baz.hello()

さっきと変わっているのは11行目だけだ。スーパークラスの指定にBarを先に書いている。これを実行すると:

^o^ > python inheritance3a.py
Hello, this is Bar.

今度はBarクラスのメソッドが実行された。スーパークラスでメソッド名が衝突している場合、サブクラスでどちらが呼び出されるのかは、継承するときに列挙した順番によるみたいだ。

クラス

クラスの定義とインスタンスの作成

クラスを定義するには、class 文を使う。もちろん定義の中身はインデントされたブロックになっている。

class Person:
    def __init__(self, name):
        self.name = name

    def greeting(self):
        print "Hello, I'm " + self.name + "!"

if __name__ == '__main__':
    andy = Person("Andy")
    andy.greeting()
    print andy.name

__init__ は特殊メソッドのひとつで、インスタンスの初期化に使われる。Ruby の initialize みたいなもんだな。ここでは name アトリビュート(インスタンス変数みたいなもの)を設定している。
メソッドはもうひとつ、greeting を定義してみた。それにしても、いちいち self を引数に含めなきゃいけないのは面倒だよなぁ。

インスタンスの作成は、クラス名を関数のように呼び出して行う。new とかはない。

実行結果:

^o^ > python person.py
Hello, I'm Andy!
Andy

1行目の出力が greeting メソッドを呼び出した結果で、2行目の出力が name アトリビュートにアクセスした結果だ。

アトリビュートの追加

アトリビュートは Ruby インスタンス変数と違って勝手に新しいものを追加できる。やってみよう。

>>> from person import Person
>>> andy = Person("Andy")
>>> andy.age
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: Person instance has no attribute 'age'
>>> andy.age = 32
>>> andy.age
32

andy.age に代入する前の段階では、アクセスしてもエラー(’age’なんてアトリビュート無いよ)が出ているが、いったん代入すると、今度は代入した値が返ってくる。これは、インスタンスにアトリビュートを追加したのであって、クラスに追加したのではないことに注意。その証拠に別のインスタンスを作ってみても age アトリビュートはない。

>>> bill = Person("Bill")
>>> bill.age
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: Person instance has no attribute 'age'

クラスの定義にないアトリビュートをインスタンスに追加できるというのは面白い。

インスタンスにメソッドを代入する

インスタンスに代入できるのはアトリビュートだけじゃない。メソッドだって代入できる。

>>> andy.hello()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: Person instance has no attribute 'hello'
>>> andy.hello = andy.greeting
>>> andy.hello()
Hello, I'm Andy!

これってどうなんだよ。さすがに違和感があるぞ。もしかして bill に andy のメソッドを代入もできるのか?

>>> bill.hello = andy.hello
>>> bill.hello()
Hello, I'm Andy!

できた。うわぁ、これは気持ちが悪い。
なんか妙に自由度が高すぎるように思うなぁ。

dir関数

dir 関数を使うとインスタンスの持っているアトリビュートの一覧を得ることができる。

>>> dir(andy)
['__doc__', '__init__', '__module__', 'age', 'greeting', 'hello', 'name']

これをみると、アトリビュート(name、age)とメソッド(greeting、hello)が一緒に並んでいる。要するに Python ではメソッドもアトリビュートの一種ってことらしい。だから代入ができるのも道理ってわけだ。