LoginSignup
8
4

More than 5 years have passed since last update.

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

Posted at

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

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4