【オブジェクト指向】Pythonの継承、抽象クラス、カプセル化について解説!

2023-07-17

Pocket

こんにちは、爽です。皆さん、いかがお過ごしでしょうか?

今回は前回に引き続き、オブジェクト指向プログラミングの為のPythonの技術について確認します。具体的には継承、抽象クラス、そしてカプセル化についての記事です。
前回の記事はこちらです。

前回はオブジェクト指向プログラミングをする為の土台となる部分の紹介でしたが、この記事ではその知識を活用した応用編です。そしてこの記事に書いてあることを知って初めてクラスの有用性を実感できるはずです。

■この記事の対象読者
・Pythonに興味がある方
・Pythonを学び始めてみたい方

・Pythonのオブジェクト指向の為のプログラミング技術について知りたい方

なお、私はPythonをAnacondaをインストールしてJupyterで実行しています。MacのAnacondaのインストール方法とJupyterの使い方は下記記事にまとめているので良かったらご参考ください。

それではどうぞ!

継承

基本形

継承とはあるクラス(親クラス)が持っているプロパティ、及びメソッドをあるクラス(子クラス)が引き継いで使えるようになることです。
具体的には子クラスをclass クラス名(親クラス名):で定義することで親クラスの性質を引き継ぐことができます。

#親クラス定義
class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')
    
    def cry(self):
        print('cry!')

    def sleep(self):
        print('sleep...')

#親クラスを継承して子クラスを作成
class DeepImpact(Horse):
    pass

#子クラスをインスタンス化すると親クラスのコンストラクタが呼び出される
h = DeepImpact(3)
h.age
# 3

#ディープインパクトは親クラスのrun()を実行できる
h.run()

# run!


こちらの例のように、継承を使うことで馬には年齢があり、普通に走ったり鳴いたり眠ることができますが、ディープインパクトという競走馬も普通の馬と同様の性質を持つ、といったことを直感的に表現することができます。
このようにプログラミングという機械的な作業を現実世界に置き換えて物事を捉える考え方がオブジェクト指向であり、Pythonに限らず、他のJavaなどのオブジェクト指向型のプログラミング言語の真骨頂であると言えます。

オーバーライド

オーバーライドとは親クラスのプロパティ、もしくはメソッドを子クラスで同名のそれを定義することです。
下記の例のようにディープインパクトは普通の馬より早く走ることが可能、ということを表現できます。

class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')
    
    def cry(self):
        print('cry!')

    def sleep(self):
        print('sleep...')

class DeepImpact(Horse):
    #ディープインパクトは普通の馬より早く走れる
    def run(self):
        print('run fast!')

h = DeepImpact(3)
h.run()
# run fast!

ちなみにオーバーライドとよく似た言葉にオーバーロードがありますが、こちらは引数、もしくは戻り値が異なる同一名称のメソッドを複数定義することです。私もよくオーバーライドとオーバーロードを混合してしまうことがあるので注意してください。

#pip install pythonlangutil

from pythonlangutil.overload import Overload, signature

from pythonlangutil.overload import Overload, signature

class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')
    
    def cry(self):
        print('cry!')

    def sleep(self):
        print('sleep...')

class DeepImpact(Horse):
    @Overload
    @signature()
    def run(self):
        print('run fast!')

    #引数が異なるrunを定義
    @run.overload
    @signature("str")
    def _(self, name):
        print('{} run fast!'.format(name))

h = DeepImpact(3)
h.run()
h.run('Deep Impact')

# run!
# Deep Impact run fast!

この記事を書いていて知りましたが、Javaと違ってPythonではオーバーロードする為には専用のモジュールをつけたり、デコレータをつけたりと一手間必要みたいですね。もしオーバーロードをやってみたい場合は上記コードを参考にしてください。

super()

super()は親クラスのメソッドを呼び出すためのキーワードです。
親クラスにも子クラスにもコンストラクタを設定したい場合に使うことが多いと思われます。
例えば下記の例は親クラスでも子クラスでもageに年齢を設定していますが、同じような処理を書くのはプログラミング的にはコードが長くなり、分かりづらくなるので良くないことです。

class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')
    
    def cry(self):
        print('cry!')

    def sleep(self):
        print('sleep...')

class DeepImpact(Horse):
    def __init__(self, age, jockey):
        #親クラスのコンストラクタと同じ記述
        self.age = age
        #==============
        self.jockey = jockey
        
    def print_self(self):
        print(self.age)
        print(self.jockey)

    def run(self):
        print('run fast!')

h = DeepImpact(3, '武豊')
h.print_self()

# 3
# 武豊


このようなケースを避けるためにsuper()キーワードを使います。super()キーワードを使うことで、親クラスのコンストラクタを呼び出してから子クラスのコンストラクタを呼び出す、といったことができます。

class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')
    
    def cry(self):
        print('cry!')

    def sleep(self):
        print('sleep...')

class DeepImpact(Horse):
    def __init__(self, age, jockey):
        #super()キーワードを使って親クラスのコンストラクタを呼び出す
        super().__init__(age)
        self.jockey = jockey
        
    def print_self(self):
        print(self.age)
        print(self.jockey)

    def run(self):
        print('run fast!')

h = DeepImpact(3, '武豊')
h.print_self()

# 3
# 武豊

多重継承

多重継承とは複数の親クラスを継承することです。これにより、複数の親クラスのプロパティとメソッドを引き継ぐことができます。ちなみにJavaは多重継承できません。
具体的には子クラスをclass クラス名(親クラス名,親クラス名):で定義すると複数の親クラスの性質を継承可能です。また、親クラスはいくつでも継承可能です。

class Horse(object):
    def __init__(self, age):
        self.age = age
    
    def run(self):
        print('run!')

class Robot(object):
    def __init__(self, name):
        self.name = name
    
    def fly(self):
        print('fly!')
        
#多重継承        
class RobotHorse(Horse,Robot):
    def __init__(self, age, name):
        #親クラスのメソッドを呼び出す場合親クラス名を指定する
        Horse.__init__(self, age)
        Robot.__init__(self, name)
        
    def print_self(self):
        print(self.age)
        print(self.name)
        
    def fight(self):
        print('fight!')

r = RobotHorse(3, 'RobotHorse')
r.print_self()
r.fight()

# 3
# RobotHorse
# fight!

抽象クラス

抽象クラスとは共通メソッド及び、必ずオーバーライドさせたいメソッド(抽象メソッド)を定義しておくようなクラスです。
具体的は親クラスをclass クラス名(metaclass=ABCMeta):で定義すると抽象クラスを定義できます。また、抽象メソッドを定義する為には、抽象メソッドの前にデコレーターの要領で@abc.abstractmethodをつけます。
抽象クラス及び抽象メソッドの使い道としては、例えば競走馬のクラスを作成したい場合、競走馬には必ず騎手を乗せる必要があるといったように、子クラスで必ず実装すべきメソッドがあること表現したい場合に使えると思います。

import abc

class Horse(metaclass=abc.ABCMeta):
    def __init__(self, age):
        self.age = age

    #抽象メソッド
    @abc.abstractmethod
    def ride(self, jockey):
        pass
    
    #共通メソッド
    def run(self):
        print('run!')

class DeepImpact(Horse):
    def __init__(self, age, jockey):
        #super()キーワードを使って親クラスのコンストラクタを呼び出す
        super().__init__(age)
        self.jockey = jockey
        
    #抽象クラスを定義していない

#インスタンス化するときにrideを定義していない旨のエラーが出力される
h = DeepImpact(3, '武豊')

# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# Input In [43], in <cell line: 1>()
# ----> 1 h = DeepImpact(3, '武豊')

# TypeError: Can't instantiate abstract class DeepImpact with abstract method ride

上記のように抽象クラスの中を継承した場合、子クラスで抽象メソッドを定義しない場合はインスタンス化の時にエラーとなります。
これを回避するためには子クラスでrideを定義します。

import abc

class Horse(metaclass=abc.ABCMeta):
    def __init__(self, age):
        self.age = age

    #抽象メソッド
    @abc.abstractmethod
    def ride(self):
        pass
    
    #共通メソッド
    def run(self):
        print('run!')

class DeepImpact(Horse):
    def __init__(self, age, jockey):
        #super()キーワードを使って親クラスのコンストラクタを呼び出す
        super().__init__(age)
        self.jockey = jockey
        
    #抽象クラスを定義
    def ride(self):
        print(self.jockey)

h = DeepImpact(3, '武豊')
h.ride()

# 武豊

カプセル化

カプセル化とはgetterとsetterを定義して、クラスの外部からクラスの変数にアクセスする際はそこを経由するようにさせることです。
getterとsetterとはクラスの外部からクラスの変数にアクセスする為のお約束的な構文です。本来、クラスの変数はクラスの外部から自由に取得及び書き換えをされるべきではありませんが、Pythonではそれを完全に制御することができません。その為、変数をカプセル化し、クラスの変数へアクセスする際の手続きを定めることが必要です。
、、ちょっと文章で書くと分かりづらいと思うので、実際に意向の説明を見ると分かりやすいと思います。

プライベート変数

カプセル化の概念を理解する為に、まずはプライベート変数の概念を理解しておく必要があります。
プライベート変数とは、クラス外部からアクセスできない変数のことです。プライベート変数を定義する為には、変数の前にアンダースコアを2つ連ねる形で変数を定義します。

class Horse(object):
    #変数名の前にアンダースコアを2つ連ねることでプライベート変数を定義
    __species = 'horse'
    def __init__(self, age):                
        #プライベート変数を定義
        self.__age = age
        
    def print_age(self):
        print('{} years old'.format(self.__age))

h = Horse(3)
h.print_age()
#クラス内からはアクセス可能
# 3 years old

#クラス変数、インスタンス変数どちらもクラス外部からはアクセスできない
h.__species
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# Input In [26], in <cell line: 1>()
# ----> 1 h.__species

# AttributeError: 'Horse' object has no attribute '__species'

h.__age
# ---------------------------------------------------------------------------
# AttributeError                            Traceback (most recent call last)
# Input In [27], in <cell line: 1>()
# ----> 1 h.__age

# AttributeError: 'Horse' object has no attribute '__age'

しかし、冒頭にも述べましたがPythonでは厳密な意味でのプライベート変数は定義できません。
プライベート変数はインスタンス名._クラス名__変数名の形式でアクセスできてしまいます。

#外部から簡単にプライベート変数にアクセスできてしまう
h._Horse__species
# 'horse'

h._Horse__age = -1
h._Horse__age
# -1

getterとsetter

getterとsetterの定義方法はそれぞれ下記です。

・getter:変数を関数のように定義し、関数名の前にデコレーターの要領で@propertyをつける
・setter:変数を関数のように定義し、関数名の前にデコレーターの要領で@変数名.setterをつける

class Horse(object):    
    def __init__(self, age):                
        self.__age = age
    
    #getter
    @property
    def age(self):
        print('getter')
        return self.__age
    
    #setter
    @age.setter
    def age(self, value):
        print('setter')
        if value < 0:
            raise minusException("This is minus!!")
        else:
            self.__age = value
            
class minusException(Exception):
    pass

h = Horse(3)
#getterの呼び出し
h.age
# getter
# 3

#setterの呼び出しとgetterで値が変わったことを確認
h.age = 5
h.age
# setter
# getter
# 5

ここまでだと「ん?この書き方だとプライベート変数を使わない場合の書き方と同じになっただけじゃないの?」と思われるかもしれませんが、上記の例ではsetterの中のif文で不正な値を設定できないようにすることで、インスタンス変数の自由な書き換えを制限しています。

h.age = -1
#if文の中でマイナス値が入った場合は処理を中断させるように制御
setter
# ---------------------------------------------------------------------------
# minusException                            Traceback (most recent call last)
# Input In [61], in <cell line: 1>()
# ----> 1 h.age = -1

# Input In [55], in Horse.age(self, value)
#      14 print('setter')
#      15 if value < 0:
# ---> 16     raise minusException("This is minus!!")
#      17 else:
#      18     self.__age = value

# minusException: This is minus!!

このようにgetterとsetterを定義することで、クラス外部からのクラスの変数へのアクセスを制限することができます。
通常アプリケーション開発ではこのようにgetterとsetterを使って変数を制御することが一般的と思われるので是非覚えておきましょう。

参考資料

この記事はUdemyのPython+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~という講座と現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイルという2つの講座を参考にさせていただき、作成しました。

Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~

Udemyより抜粋

当講座は最低限のWeb開発の知識を知っていることが前提とはなりますが、とにかくPythonの説明とFlask開発の為の説明が充実しているのでおすすめです。
28時間に及ぶ長丁場の講座にはなりますが、絶対に聞く価値がある講座です。
これをマスターすればPythonでどんなアプリケーションでも作ることができると思います。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

とにかく説明が充実している

Webアプリ開発におけるフレームワークがなぜ有益なのか知ることができる

セキュリティ対策、Ajaxなどの技術も知れる

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

画像に alt 属性が指定されていません。ファイル名: a1ee3d048d6aa860662b6f58c4aa167f-1024x328.png
Udemyより抜粋

当講座はPythonの基礎から応用まで幅広く学べる講座なのでおすすめです。
この講座の講師はとにかくPythonについての知識が豊富ですし、話も適度な速さで聞き取りやすいです。さすがのシリコンバレーです。
また、最後の方に機械学習で使うライブラリについても解説があるので、データサイエンス・AIについても多少知ることができます。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

シリコンバレーで働いているということもあり、講師のPythonの知識が豊富

話も適度な速さで聞き取りやすい

コードの意味だけでなく、それをどう応用するかまで解説してくれる

なお、Udemyについては以下の記事でまとめていますのでご参考ください。

まとめ

ということで、今回はPythonの継承、抽象クラス、そしてカプセル化について解説しました。
オブジェクト指向のプログラミングをする上でこの辺りの知識は覚えておく必要があるので、構文を全て覚えるのは大変なので、開発の中で都度確認すればいいですが、それぞれがどういう意味なのかはここで把握しておきましょう。
では、今回はここまでとさせていただきます。