Python
Julia

Pythonの複数クラス継承をJuliaで書いてみる

PythonコードをJuliaに移植するときに役に立ちそうなことをまとめておきます。

この記事は

"PythonっぽいJulia:PythonコードのclassをJuliaコードへ移植する"

https://qiita.com/cometscome_phys/items/0cc0ab0b43e1bb48a1ea

の続きとなっています。

今回は、Pythonにおけるクラス継承、特に、多重クラス継承が行われているコードをJuliaに移植してみます。

なお、これが最適かどうかはよくわかっていませんので、コメント等歓迎です。


バージョン

Julia 1.1

Python 3系


Pythonの複数クラス継承

"[Python]クラス継承(super)"

https://qiita.com/Kodaira_/items/42dfe18c81af98bf0db3

の記事にあるPythonコードをJuliaコードに移植してみることとします。


一つのクラスの継承


Python

Creatureクラスというクラスには能力値が入っており、このCreatureクラスをもとに、Warrior、Magicianクラスを作っています。

WarriorとMagicianは一部の能力値が異なっています。

以下は(https://qiita.com/Kodaira_/items/42dfe18c81af98bf0db3) からの引用です。

引用始まり:

class Creature(object):

def __init__(self, level=1, weapon=None):
self.level = level
self.hp = 0
self.mp = 0
self.attack = 0
self.defence = 0
self.weapon = weapon
self.job = "neet"

def status(self):
return "Job:{} | HP:{} | MP:{} | Atk:{} | Def:{} | Weapon:{}".format \
(self.job, self.hp, self.mp, self.attack, self.defence, self.weapon)

class Warrior(Creature):
def __init__(self, level):
super().__init__(level)
self.attack += 3 * level
if self.weapon is None:
self.weapon = "sword"
if self.job == "neet":
self.job = "Warrior"
else: self.job += "Warrior"

class Magician(Creature):
def __init__(self, level):
super().__init__(level)
self.mp += 4 * level
if self.weapon is None:
self.weapon = "rod"
if self.job == "neet":
self.job = "Magic"
else: self.job += "Magic"

:引用終わり

これを実行すると、

>>> print(Warrior(5).status())

Job:Warrior | HP:0 | MP:0 | Atk:15 | Def:0 | Weapon:sword
>>> print(Magician(5).status())
Job:Magic | HP:0 | MP:20 | Atk:0 | Def:0 | Weapon:rod

となります。


Julia

次に、Juliaのコードです。

まず、abstract typeとしてCreatureタイプを作ります。

abstract type Creature

end

これはabstract typeなのでフィールドを持てません。そこで、別のmutable structを作ります。

mutable struct Creature_values

level
hp
mp
attack
defence
weapon
job
Creature_values(level,weapon=nothing)= new(level,0,0,0,0,weapon,"neet")
end

そして、Creatureタイプの子のタイプであるWarriorやMagicianは、このCreature_valuesタイプをフィールドに持つことにしましょう。

mutable struct Warrior <: Creature

creature::Creature_values
end

mutable struct Magician <: Creature
creature::Creature_values
end

Creatureタイプを親に持つタイプに対して、

function status(self::Creature)

return "Job:$(self.creature.job) | HP:$(self.creature.hp) | MP:$(self.creature.mp) | Atk:$(self.creature.attack) | Def:$(self.creature.defence) | Weapon:$(self.creature.weapon) \n"
end

という関数を定義しておけば、WarriorタイプもMagicianタイプもこの関数を利用することができます。

次に、それぞれのタイプが持つフィールドの初期値を設定しなければなりません。

また、MagicianかつWarriorのようなタイプを今後作ることを考えると、転職して能力が増加するようにしたいところです。

これは、タイプを作る際にoptional変数を入れておけば実現できます。

mutable struct Warrior <: Creature

creature::Creature_values

function Warrior(level,weopon=nothing,creature::Creature_values = Creature_values(level,weopon))
creature.attack += 3*level
if creature.weapon == nothing
creature.weapon = "sword"
end
if creature.job == "neet"
creature.job = "Warrior"
else
creature.job *= "Warrior"
end
new(creature)
end
end

mutable struct Magician <: Creature
creature::Creature_values
function Magician(level,weopon=nothing,creature::Creature_values = Creature_values(level,weopon))
creature.mp += 4*level
if creature.weapon == nothing
creature.weapon = "rod"
end
if creature.job == "neet"
creature.job = "Magic"
else
creature.job *= "Magic"
end
new(creature)
end
end

Warrior,Magicianタイプは一つの引数で作成するとlevelを入力することになります。

また、初期装備を変更することもあるので、weoponをoptional変数にしました。

そして、Warriorタイプの初期値として、Creature_valuesタイプのcreatureをoptional変数としました。

これにより、

print(status(Warrior(5)))

print(status(Magician(5)))
print(status(Warrior(5,"katana")))

をすると、

Job:Warrior | HP:0 | MP:0 | Atk:15 | Def:0 | Weapon:sword 

Job:Magic | HP:0 | MP:20 | Atk:0 | Def:0 | Weapon:rod
Job:Warrior | HP:0 | MP:0 | Atk:15 | Def:0 | Weapon:katana

と出力されます。


複数クラスの継承


Python

次に、複数クラスの継承を行なって、MagicWarriorなどを作ってみましょう。

以下は(https://qiita.com/Kodaira_/items/42dfe18c81af98bf0db3) からの引用です。

引用始まり:

class MagicWarrior(Warrior, Magician):

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

class WarriorMagic(Magician, Warrior):
def __init__(self, level):
super().__init__(level)

:引用終わり

この二つのクラスはそれぞれWarriorとMagicianを継承していますが、順番が異なっています。

これを実行すると、

>>> print(MagicWarrior(5).status())

Job:MagicWarrior | HP:0 | MP:20 | Atk:15 | Def:0 | Weapon:rod
>>> print(WarriorMagic(5).status())
Job:WarriorMagic | HP:0 | MP:20 | Atk:15 | Def:0 | Weapon:sword

となります。MagicWarriorかWarriorMagicで初期化の順番が異なっていることがわかると思います。


Julia

上のような、二つのタイプを合わせ持つようなタイプを作るにはどうすれば良いでしょうか?

実は、上で書いたJuliaのコードではタイプの初期値の設定が可能になっているために、意外と簡単に実装することができます。

実装は、

mutable struct MagicWarrior <: Creature

creature::Creature_values
function MagicWarrior(level,weopon=nothing)
magicwarrior = Warrior(level,weopon,Magician(level,weopon).creature)
new(magicwarrior.creature)
end
end

mutable struct WarriorMagic <: Creature
creature::Creature_values
function WarriorMagic(level,weopon=nothing)
warriormagic = Magician(level,weopon,Warrior(level,weopon).creature)
new(warriormagic.creature)
end
end

です。MagicWarriorタイプでは、Warrior(level,weopon,Magician(level,weopon).creature)で、Magicianタイプの初期値を持つWarriorタイプ、というものを定義することができています。つまり、MagicianがWarriorに転職した、ということですね。

タイプを継承するのではなく、その中身であるcreatureを持ってきていることになります(移譲、というのでしょうか)。

WarriorMagicでは、Warriorタイプの初期値を持つMagicianタイプを定義しています。これは、WarriorがMagicianに転職した、ということになります。

実際、

print(status(MagicWarrior(5)))

print(status(WarriorMagic(5)))

実行してみると、

Job:MagicWarrior | HP:0 | MP:20 | Atk:15 | Def:0 | Weapon:rod 

Job:WarriorMagic | HP:0 | MP:20 | Atk:15 | Def:0 | Weapon:sword

となっており、転職して新しいメイン職を得たようになっています。

なお、

print(status(MagicWarrior(5,"body")))

は、

Job:MagicWarrior | HP:0 | MP:20 | Atk:15 | Def:0 | Weapon:body 

となっており、装備品の変更も可能です。


まとめ

以上をまとめますと、Juliaの場合には、メソッドは親タイプに対して定義することで共通に使うことができ、共通の値に対しては、「タイプを継承する」のではなく、そのフィールドの値を初期値として持ってくること(移譲?)で、Pythonのクラス継承と似たようなことができるようです。