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のクラス継承と似たようなことができるようです。