『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

クラスの継承

クラスの継承

一般的にオブジェクト指向言語においては、あるクラスをひな型として部分的に機能を改造したクラスを作ることをクラスの継承 (inheritance)とよびます。
 
ひな型となるクラスを スーパークラス (親クラス) とよび、スーパークラスをもとにつくられたクラスを サブクラス (子クラス) といいます。
 
以前の記事 で定義した Rectanglesクラスを再掲し、それをひな型として、サブクラスを設計してみます。

新しいメソッドの追加

サブクラスは class文

class サブクラス名(スーパークラス名):

によって定義することができます。サブクラスは初期化メソッドも含めて、スーパークラスのすべてのメソッドをそのまま受け継ぎます。サブクラスのスイート(機能を記述する場所)に新しい名前のメソッドを定義すると、サブクラスから生成されたインスタンスは、スーパークラスのメソッドに加えて、新しいメソッドを使えるようになります。

# PYTHON_CLASS_INHERITANCE

# In[1]

# Rectanglesクラスの定義
class Rectangles:
    def __init__(self, a, b):
        self.width = a
        self.length = b

    def area(self):
        return self.width * self.length

# Rectanglesクラスを継承してRectangles_2クラスを作成
class Rectangles_2(Rectangles):
    
    # 新しいメソッドの追加
    def perimeter(self):
        return self.width * 2 + self.length * 2

# Rectangles_2クラスのインスタンスを生成
rec = Rectangles_2(5, 10)

# スーパークラスRectangleのメソッドを使用
print("面積", rec.area())

# サブクラスRectangle_2で追加されたメソッド
print("周長", rec.perimeter())

# 面積 50
# 周長 30

上のサンプルコードではサブクラスに perimeter() という、周の長さを返すメソッドが追加されています。Rectangles_2クラスから作られたインスタンスは、もとの Rectanglesクラスのメソッドを用いて面積を計算することもできるし、Rectangles_2クラスで追加された新しいメソッドを使って周長を求めることもできます。

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

サブクラスのスイートに、スーパークラスで定義されているものと同じ名前でメソッドを定義すると、そのメソッドは上書きされます。つまり、サブクラスから作られたインスタンスは、この上書きされたほうのメソッドを実装し、スーパークラスのメソッドは呼び出されません。これを メソッドのオーバーライド とよびます。以下のサンプルコードでは、Rectanglesクラスの area()メソッドを上書きして、「面積は~です」という文字列を返すように修正します。

# In[2]

# Rectanglesクラスを継承してRectangles_3クラスを作成
class Rectangles_3(Rectangles):
    #スーパークラスのarea()メソッドを上書き
    def area(self):
        return "面積は" + str(self.width * self.length) + "です。"

# Rectangles_3クラスのインスタンスを生成
rec = Rectangles_3(5, 10)

# 上書きされたメソッドを使用
print(rec.area())

# 面積は50です。

super()関数によるスーパークラスの呼び出し

初期化メソッドについても、上述のような方法でオーバーライドすることができますが、スーパークラスの設計内容に変更があったときにサブクラスの動作に不具合が生じる可能性もあります。そこで一般的には、初期化メソッドに変更を加えるときには、super()関数 でスーパークラスを呼び出します。以下のサンプルコードでは、Rectanglesクラスをもとに Squares という正方形クラスを設計し、初期化メソッドを上書きしています。

# In[3]

# Rectanglesクラスを継承してSquaresクラスを作成
class Squares(Rectangles):

    # super()関数でスーパークラスをよびだし、
    # 初期化メソッドの引数a,bにxを渡す
    def __init__(self, x):
        super().__init__(x, x)

# Squaresクラスのインスタンスを作成
sq = Squares(10)

# 正方形の面積を計算
print(sq.area())
# 100

長方形は二辺の長さを指定する必要がありますが、正方形ならば一辺の長さを与えれば性質が定まります。したがって、Squaresクラスのインスタンスを作るときには、x という1つの引数だけを与えるように設計しますが、

    def __init__(self, x):
        super().__init__(x, x)

という記述によって、x をスーパークラスの引数 a, b に渡しています。つまり、a = x, b = x という代入が行われ (a, b は等しい値をもちます)、これをスーパークラスのインスタンス変数(データ属性)の self.width と self.length に渡して、area()メソッドの計算に用いるという仕組みになっています。

自動車販売店の在庫管理システム

クラス継承の練習として、自動車販売店の在庫管理システムを構築してみます。以下の要件を満たすクラス階層を設計します。
 
・Vehicle
 基底クラス(車両全般)
 属性:メーカー(manufacturer)、モデル(model)、年式(year)、価格(price)
 メソッド:車両情報を表示する get_info()

・Car
 乗用車クラス(Vehicle クラスから継承)
 属性:ボディタイプ(body_type)
 メソッド:なし

・Motorcycle
 オートバイクラス(Vehicle クラスから継承)
 属性:エンジンサイズ(engine_size)
 メソッド:なし

・ElectricCar(電気自動車)クラスを Car クラスと Vehicle クラスの両方から多重継承して定義します。
 属性:バッテリー容量(battery_capacity)
 メソッド:なし

・Inventory(在庫)クラスを定義します。
 属性:車両リスト(vehicles)(Vehicleオブジェクトのリスト)
 メソッド:
  add_vehicle(vehicle):在庫に車両を追加する
  remove_vehicle(vehicle):在庫から車両を削除する
  search_vehicle_by_maker(manufacturer):メーカーで車両を検索し、一致する車両のリストを返す
  search_vehicle_by_price(price):価格で車両を検索し、指定価格以下の車両のリストを返す
 
クラスの実装は以下のようになります。

# VEHICLE

# In[1]

# 基底クラス(車両)
class Vehicle:
    def __init__(self, manufacturer, model, year, price):
        self.manufacturer = manufacturer
        self.model = model
        self.year = year
        self.price = price

    def get_info(self):
        print(f"{self.manufacturer} {self.model} ({self.year}) - ¥{self.price}")

# 乗用車クラス
class Car(Vehicle):
    def __init__(self, manufacturer, model, year, price, body_type):
        super().__init__(manufacturer, model, year, price)
        self.body_type = body_type

# オートバイクラス
class Motorcycle(Vehicle):
    def __init__(self, manufacturer, model, year, price, engine_size):
        super().__init__(manufacturer, model, year, price)
        self.engine_size = engine_size

# 電気自動車クラス
class ElectricCar(Car, Vehicle):
    def __init__(self, manufacturer, model, year, price, body_type, battery_capacity):
        super().__init__(manufacturer, model, year, price, body_type)
        self.battery_capacity = battery_capacity

# 在庫クラス
class Inventory:
    def __init__(self):
        self.vehicles = []

    def add_vehicle(self, vehicle):
        self.vehicles.append(vehicle)

    def remove_vehicle(self, vehicle):
        self.vehicles.remove(vehicle)

    def search_vehicle_by_maker(self, manufacturer):
        matching_vehicles = [vehicle for vehicle in self.vehicles if vehicle.manufacturer == manufacturer]
        for vehicle in matching_vehicles:
            vehicle.get_info()

    def search_vehicle_by_price(self, price):
        matching_vehicles = [vehicle for vehicle in self.vehicles if vehicle.price <= price]
        for vehicle in matching_vehicles:
            vehicle.get_info()

クラス設計が完了したので、さっそくテストしてみましょう。
まずは、各クラスのインスタンスを作成します。

# In[2]

car1 = Car("Toyota", "Camry", 2022, 2500000, "セダン")
car2 = Car("Honda", "Civic", 2023, 2200000, "ハッチバック")
car3 = Car("Toyota", "Corolla", 2023, 2400000, "セダン")
motorcycle1 = Motorcycle("Kawasaki", "Ninja", 2022, 1200000, 1000)
electric_car1 = ElectricCar("Tesla", "Model 3", 2023, 4000000, "セダン", 75)
electric_car2 = ElectricCar("Nissan", "Leaf", 2022, 3200000, "ハッチバック", 60)

inventory = Inventory()
inventory.add_vehicle(car1)
inventory.add_vehicle(car2)
inventory.add_vehicle(car3)
inventory.add_vehicle(motorcycle1)
inventory.add_vehicle(electric_car1)
inventory.add_vehicle(electric_car2)

在庫の全車両情報を表示してみましょう。

# In[3]

# 在庫の全車両情報を表示
for vehicle in inventory.vehicles:
    vehicle.get_info()

# Toyota Camry (2022) - ¥2500000
# Honda Civic (2023) - ¥2200000
# Toyota Corolla (2023) - ¥2400000
# Kawasaki Ninja (2022) - ¥1200000
# Tesla Model 3 (2023) - ¥4000000
# Nissan Leaf (2022) - ¥3200000

トヨタの車両だけを表示してみます。

# In[4]

# トヨタの車両情報を表示
inventory.search_vehicle_by_maker("Toyota")

# Toyota Camry (2022) - ¥2500000
# Toyota Corolla (2023) - ¥2400000

 全車両の中から価格が 2300000 円以下の車両を検索して情報を表示してみましょう。

# In[5]

# 価格が2500000円以下の車両を検索して情報を表示
inventory.search_vehicle_by_price(2500000)

# Honda Civic (2023) - ¥2200000
# Kawasaki Ninja (2022) - ¥1200000

ホンダのシビックを在庫から削除してみます。

# In[6]

inventory.remove_vehicle(car2)

# 在庫の全車両情報を再度表示
for vehicle in inventory.vehicles:
    vehicle.get_info()

# Toyota Camry (2022) - ¥2500000
# Toyota Corolla (2023) - ¥2400000
# Kawasaki Ninja (2022) - ¥1200000
# Tesla Model 3 (2023) - ¥4000000
# Nissan Leaf (2022) - ¥3200000

コメント

  1. HNaito より:

    下記は誤植と思われますので、ご確認ください。
    「新しいメソッドの追加」の説明文で、
    class サブクラス名( スーパークラス名 ) → class サブクラス名( スーパークラス名 ) :

    • あとりえこばと より:

      おお! 本当ですね!
      「:」ひとつ抜けても、コードは動かないので注意が必要ですね。
      それにしても、よく気づかれましたね …
      助かりました。m(_ _)m