memopy

pythonで作ってみました的なブログ

python クラスの定義と継承とは

python クラスの定義と継承とは

前回の記事は、tkinterのドキュメントのサンプルスクリプトとして記載されている"A Simple Hello World Program"が難しすぎる。という記事を投稿した。
前の記事
python tkinterのクラス化手法によるGUI作成 - memopy
今回は、pythonのクラスとはなにか。クラスの継承とはなにかについてまとめたいと思う。
特に、クラスの継承はpython2とpython3で方法が一部異なるため、この点を整理したい。

class クラスとは

はじめにクラスの基礎について触れたい。
クラスとは、データ(属性)や関数(メソッド)を定義できるものである。
もっとも単純なクラスの定義をすると次のようになる。

class person:
    pass

これで何も属性やメソッドが定義されていない空のpersonクラスが定義された。
このpersonクラスを使ってみる。

me = person()

これでmeの中にpersonクラスによって定義されたオブジェクトが作成されたことになる。
ただし、personクラスには何も定義されていないので、meは空のオブジェクトである。
meは、空のオブジェクトであるが、後から属性を定義することはできる。

# height(身長)という属性を新規に定義し、175を格納する。
me.height = 175
print(me.height)
# →175と表示される

personクラスから作成したmeオブジェクトには、heightという属性が定義されたが、元のpersonクラスにheightという属性は定義されない。

someone = person()
print(someone.height)
# →「heightという属性はないよ」というエラーメッセージが表示される

初期化メソッド __init__ と self

personクラスに毎回毎回、heightという属性を定義したいのであれば、はじめから、personクラスに定義しておきたくなる。
そして、クラスの初期設定をする場所が、初期化メソッド __init__ である。

class person:
    def __init__(self):
        self.height = 170

__init__は、特殊なメソッドで、クラスが呼び出されたときに始めに呼び出されるメソッドである。そして、__init__メソッドの第1引数には必ずselfを入れなければならない。
selfを入れることによって、このpersonクラス自身がselfとして渡されるのである。そして、selfに対して属性heightを定義し、あらかじめ170という数値を格納した。

me = person()
print(me.height)
# →170と表示される
someone = person()
print(someone.height)
# →170と表示される

今後は、heightという属性はpersonクラスにあらかじめ定義することができたが、どんなオブジェクトを作っても170という数値が入ってしまう。
ただし、上書きすることは可能だ。

someone.height = 165
print(someone.height)
# →165と表示される

クラスの引数を定義

heightという属性をあらかじめ定義できることはわかった。しかし、heightの値はオブジェクトごとに変更したいし、後から上書きするのもめんどくさいので、クラスからオブジェクトを作成するときに引数として渡したい。
そのためには、初期化メソッドに引数の定義をする。

class person:
    def __init__(self,hgt):
        self.height = hgt

初期化メソッド__init__の第1引数がselfであることは変わらないが、クラスからオブジェクトを作成するときに、受け取る引数を第2以降に記述する。(クラスの属性heightと区別するため、引数の仮変数はhgtとした。)
そして、初期化メソッド内で属性heightに受け取った引数hgtを定義してあげるのだ。

me = person(180)
print(me.height)
# →180と表示される

クラスにメソッドを定義

personクラスにはheight(身長)という属性が定義されている。今度は、このpersonクラスに対してメソッドを定義する。
今回定義するのは、cm単位で渡されたheight(身長)という値をメートルに単位変換して表示するものである。

class person:
    def __init__(self,hgt):
        self.height = hgt
    def hgt_cm2m(self):
        print( str(self.height / 100.0) + "m" )

クラスに定義するメソッドについても、第1引数には必ずselfを定義しなければならない。このメソッドは、引数をとらないが、引数を取るときには、第2以降に定義すればよい。

me = person(180)
print(me.height)
# →180と表示される
print(me.hgt_cm2m())
# →1.8mと表示される

クラスの継承

今度は、このpersonクラスが既に定義されているとして、このpersonクラスを土台に、新たなクラスへ改良する。
これがクラスの継承である。
はじめに、クラスを継承するが、継承元(スーパークラスという)の振る舞いを何も変更しない。

class person_mod(person):
    pass

クラスを継承するには、継承したいクラス名を引数として指定する。
この場合、person_modという新たなクラスは、personクラスを継承するということになる。
この、personクラスを継承したperson_modクラスは、personクラスとまったく同じ振る舞いをする。

someone = person_mod(160)
print(someone.height)
# →160と表示される
print(someone.hgt_cm2m())
# →1.6mと表示される

クラスの継承によるオーバーライド(上書き)

上記のまったく同じ振る舞いをするクラスでは、クラスを継承する意味がないので、継承したクラスを変更したい。
今までは第1引数のheight(身長)しか定義していなかったので、第2引数としてweight(体重)を受け取り、これを定義したいとする。

class person_mod(person):
    def __init__(self,hgt,wgt):
        self.weight = wgt

このクラスを試してみる。

me = person_mod(175,65)
print(me.weight)
# →65と表示される
print(me.height)
# →「heightという属性はないよ」というエラーメッセージが表示される
print(me.hgt_cm2m())
# →「このメソッドは定義されてるけど、heightという属性がないと」というエラーメッセージが表示される

これはどういうことだろうか。
実は、クラスを継承した時に、__init__メソッドを作成したが、このとき、スーパークラスであるpersonの__init__メソッドを上書きしてしまった。これをオーバーライドという。
オーバーライドしてしまったため、personクラスでは定義されていた属性heightがなくなってしまい、結果、単位変換をするhgt_cm2mメソッドに渡す属性heightがなくなった。なお、__init__メソッドはオーバーライドされたが、単位変換するhgt_cm2mはなくなっていない。

スーパークラスの参照

せっかくクラスを継承したのに、スーパークラスの属性などが上書きされては困る。そこで、スーパークラスの属性はそっくりそのまま、オーバーライドせずに継承するということを明示的にやらなければならない。
そのためには、スーパークラスの初期化メソッド__init__を呼び出す必要がある。

class person_mod(person):
    def __init__(self,hgt,wgt):
        person.__init__(self,hgt) # この1文を追加
        self.weight = wgt

このように、継承した初期化メソッドでスーパークラスの初期化メソッド(person.__init__)を呼び出し、スーパークラスの初期化メソッドに必要な引数を渡してあげる必要がある。
こうすることで、スーパークラスの初期化メソッドをオーバーライドすることなく継承できる。

me = person_mod(175,65)
print(me.height)
# →175と表示される
print(me.weight)
# →65と表示される
print(me.hgt_cm2m())
# →1.75mと表示される

python3におけるsuper()メソッドを使用したクラスの継承

python3では、super()メソッドを使用してスーパークラスを参照できる。

class person_mod(person):
    def __init__(self,hgt,wgt):
        super().__init__(hgt) # このとき、第1引数selfが必要ない点に注意
        self.weight = wgt

このように、python3の文献では、super()メソッドを使用していることが多いため、結果、python2とは異なる記述になっている。
そのため、python2でクラス継承をしているスクリプトを応用する場合は注意が必要だ。

継承したクラスに新たなメソッドの定義

先ほどは、クラスを継承して新たな属性を定義したが、当然、新たなメソッドも定義できる。
今度は、身長と体重からBMIを計算するメソッドを定義したい。

class person_mod(person):
    def __init__(self,hgt,wgt):
        person.__init__(self,hgt)
        self.weight = wgt
    def cal_bmi(self):
        # BMI = 体重 / 身長の2乗
        return self.weight / (self.height / 100.0 ) ** 2

これを試してみる。

me = person_mod(175,65)
print(me.cal_bmi())
# →21.224489795918366と表示される

このように、継承したクラスに対して新たなメソッドを定義することができる。

次回は、tkinterにおけるクラスの継承について、具体的なサンプルスクリプトを例示して整理する。
python tkinter クラス継承のサンプルスクリプト - memopy