【Python】遊戯王で理解するクラスの継承
~ Pythonを勉強中だけど、クラスがわからない、そんなあなたへ ~
本記事は、以前に書いた記事、 (【Python】遊戯王で理解するオブジェクト指向)の第2段に当たります。 slmnphmet1-2.hatenablog.com
インスタンス変数が「カードの基本情報」に、メソッドが「カードの効果」に対応していることは、前回ご紹介したかと思います。 今回は、クラスを継承するとは何か、ということを、またも遊戯王を引き合いに出しながら確認していきます。
「Pythonを勉強中だけどクラスってよくわからないんだよね〜」という人、 もしくは、クラスはわかってきたんだけど、継承の意味やメリットがわからない!という人が、 少しでもイメージをつかんでくれたら嬉しいです。
継承とは、 カードの種類を指定すること
継承とは、親クラス(カードの種類)の情報を、子クラス(個々のカード)に渡すことだと思うとわかりやすいでしょう。 今回は、強欲な壺などの魔法カードを例に考えていきたいと思います。
「魔法カード」をクラスで表現してみる
遊戯王において、魔法カードは主に手札から発動し、効果の適用が終わったら墓地に置かれます(永続魔法や装備魔法などは一旦考えないことにします)。 pythonの言葉で書くと、以下のようにすれば良いでしょう。
class Magic : def __init__(self) : self.cardtype = "魔法カード" self.name = None def activate(self) : hands.remove(self.name) self.effect() cemetary.append(self.name) def effect(self) : pass
Magicには、メソッドが二つ定義してあります。 activate(発動)では、手札(hands というリスト)から自分自身(self.name)を取り除き、 効果を適用(self.effect)し終えたら墓地(cemetary)に置くという意味です。
インスタンス変数のself.name とメソッドのeffectには、現状何も書かれていません。 なぜならば、定義したい魔法カードごとに名前や効果が異なるためです。 逆に言えば、self.nameとeffectさえ定義すれば、(activateをいちいち書き直さなくても)好きな魔法カードが定義できることになります。 以下で具体的に見てみましょう。
魔法カードのルールを継承させてみる
例として、魔法カードクラス(Magic)を継承し、強欲な壺(英名 : Greedy Pot)というクラスを定義してみることにします。
class GreedyPot(Magic) : def __init__(self) : self.name = "強欲な壺" def effect(self) : hands.append(decks[0]) decks.remove(decks[0]) hands.append(decks[0]) decks.remove(decks[0])
見ての通り、強欲な壺(GreedyPot)というクラスには、発動(activate)というメソッドが書かれていません。 しかし、魔法カードであることを指定(Magic クラスを継承)しているので、発動(activate)の処理を行うことができます。 activateの中ではeffectを行う(効果を適用する)処理があります。強欲なツボの効果(effect)は2枚ドローですので、 デッキ(配列 decks)から、カード2枚を取り出し、手札(配列 hands)にその二枚を加えます。
それでは、実際に強欲な壺というカードを発動(card.activate())してみましょう。 デッキにカードが2枚、手札に強欲なツボがあるとき、activateの後にどうなるかというと...
decks = ["グレムリン", "ブラック・マジシャン"] hands = ["強欲な壺"] cemetary = [] card = GreedyPot() card.activate() print(f"手札 : {hands}") print(f"デッキ : {decks}") print(f"墓地 : {cemetary}")
手札 : ['グレムリン', 'ブラック・マジシャン'] デッキ : [] 墓地 : ['強欲な壺']
手札が2枚増えて、墓地に強欲な壺が置かれるはずです。
同じように、成金ゴブリン(英名 : Upstart Goblin)というカードを定義してみましょう。 (ライフ回復効果は、今回省きます。)
class UpstartGoblin(Magic) : def __init__(self) : self.name = "成金ゴブリン" def effect(self) : hands.append(decks[0]) decks.remove(decks[0])
やはり、逐一activateを書かなくて良いので、手間が省ける上、コードが見やすくなりますね。
decks = ["グレムリン", "ブラック・マジシャン"] hands = ["成金ゴブリン"] cemetary = [] card = UpstartGoblin() card.activate() print(f"手札 : {hands}") print(f"デッキ : {decks}") print(f"墓地 : {cemetary}")
ちなみに発動後はこんな感じです。
手札 : ['グレムリン'] デッキ : ['ブラック・マジシャン'] 墓地 : ['成金ゴブリン']
このように、親クラス(Magic)を継承することで、親と共通する処理(activate)を省き、 コードが冗長になるのを防ぐことができます。Magicクラスを継承したときに、 魔法カードとしてのルールが適用されるんだということが、 コードを見る人にも一目瞭然ですね。
super()により、「魔法カード」としての基本情報を継承
しかしこのままでは、継承しきれていないものがあります。それは、親クラス(Magic)のインスタンス変数です。
親クラスのMagicには、インスタンス変数として、cardtypeが定義されていました。これを呼び出せば、「魔法カード」と出力されます。
magic = Magic()
print(magic.cardtype)
しかし、継承後に新しくコンストラクタを定義すると、この情報が失われます。 以下はエラーになります。
card = GreedyPot()
print(card.cardtype)
強欲な壺は魔法カードですから、cardtypeにアクセスしたときに、「魔法カード」と出力されて欲しいですよね。 そこで、super()の出番です。
class GreedyPot(Magic) : def __init__(self) : super().__init__() self.name = "強欲なツボ" def effect(self) : hands.append(decks[0]) decks.remove(decks[0]) hands.append(decks[0]) decks.remove(decks[0]) card = GreedyPot() print(card.cardtype)
今度は正しく「魔法カード」と出力されるはずです。 def init(self)直下に、super().init()を置いてやれば、 新しくコンストラクタを定義したときでも、 親クラス(Magic)のカードのインスタンス変数(カードの基本情報) を引き継ぐことができます。
まとめ
クラスの継承とは、カードの種類を指定し、そのルールを子クラス(個々のカード)に適用させることと対応しています。 また、super()は、継承後にも親クラスの情報を引き継ぐのに用いられます。 クラスの継承によって、冗長なコードが回避でき、複数の子クラス(個々のカード)を定義するときに見やすくなりますね。
※ 遊戯王に例えると、イメージを掴みやすくなる分、正確さを欠きます。概観が掴めたという人は、是非オブジェクト指向の厳密な理解に励んでください。
最後に、参考になるサイトのリンクを貼っておきます。一読いただき、ありがとうございました。