クラス#

これまでstr型,int型,float型,list型などの様々な型(Type)を扱ってきましたが,これらのことを 組み込み型 と呼びます.これに対して,ユーザーが自分で定義して作る新しい方を ユーザー定義型 と呼びます.クラスとはこのユーザー定義型のことです.使い方を学んでいきましょう.

クラスとは何か#

設計書に従ってロボットが生産される様子をイメージしてください.この設計書は少し特殊で,「その時々で必要な値」を入れることが可能になっています.必要な値を入れた設計書から,「設計書に書かれた製造手順」に従って実際のロボットを作成します.

また,この設計書には「作られたロボットはどのような動作ができるのか」についても書かれています.この動作の中では,他の物体を掴んで動かす処理や,自分自身の内部情報を読み取ってから何かを出力する処理が含まれています.

ここでいう設計書がクラスであり,「設計書に書かれた製造手順」がコンストラクターです.また,「作られたロボットはどのような動作ができるのか」はメソッドとして実装されます.

クラスの作り方#

属性を持ったクラス#

ゲームキャラクターのクラスを作成しましょう.ここで,HumanクラスはHP,MP,攻撃力,防御力,スピードを持っていることにします.全てデフォルトで100の値を持っていることにしましょう.

class Human:
    def __init__(self, hp=100,mp=100, attack=100, defence=100, speed=100):
        self.hp = hp
        self.mp = mp
        self.a = attack
        self.d = defence
        self.s = speed

これでステータスだけ持ったキャラクターの設計図が作成できました. __init__メソッドのように__で囲まれたメソッドは 特殊メソッド と呼ばれ,重要な役割があります.この場合は「インスタンスを作る際に,初期化作業を行う」のが__init__の役割です.なのでこれを コンストラクタ とも呼びます.

コンストラクタを含めて,メソッドの第二引数以降は普通の関数と同じです.では第一引数のselfは何かというと,「このメソッドを呼び出すオブジェクト」が勝手に入力される引数です.

この設計図(クラス)をもとに実体(インスタンス)を作成します.クラスの初期化は,クラスを関数のように呼び出すことで行います.また,その際に実際には __init__メソッドが呼ばれていることに注意してください.

taro = Human() # 平凡な人間
yusya = Human(1000,1000, 300, 200,300) # パワフルな人間
madoshi = Human(80,1200, 200, 80,200) # MPが高いがスタミナがない人間 

madoshi.hp
80

同じクラスでもパラメータを変えるだけで様々なバリエーションが作れることがわかると思います.また,このクラスはユーザー定義型なので,type関数でインスタンスを確認すると:

type(taro)
__main__.Human

このようにHuman型であることがわかります.

メソッドを持ったクラス#

クラスは値と専用関数をまとめたものとも言えます.今 __init__メソッドで初期化時に設定する インスタンス属性 (インスタンスごとに値を変えられる属性)を設定しました.次はその他のメソッドを設定して,この型専用の関数を用意します.

ここでは,「自分のattackの数値 - 相手のdefence数値」分だけ相手のhpを減少させる攻撃「メガトンパンチ」をメソッドとして実装します.また,同様にして,「自分のattackの数値 - 相手のdefence数値/2」分だけ相手のhpを減少させる攻撃「メガトンキック」をメソッドとして実装します.

class Human:
    def __init__(self, hp=100,mp=100, attack=100, defence=100, speed=100):
        self.hp = hp
        self.mp = mp
        self.a = attack
        self.d = defence
        self.s = speed
    
    def megaton_punch(self, target):
        """
        targetはHumanインスタンス
        """
        damage = self.a - target.d
        if damage < 0:
            damage = 0
        
        target.hp -= damage
        return target 

    def megaton_kick(self, target):
        """
        targetはHumanインスタンス
        """
        damage = self.a - target.d/2
        if damage < 0:
            damage = 0
        
        target.hp -= damage
        return target 
    
ichiro = Human(attack=110) 
jiro = Human() 

__init__と同様に,第一引selfは「このメソッドを呼び出したオブジェクト(インスタンス)」を指します.そのため,下の例ではichiromegaton_punchselfに紐づけられています.

jiro = ichiro.megaton_punch(jiro)

jiro.hp
90

メソッドはクラスに束縛されていることを除けば,関数と全く同じです.普通のメソッドは第一引数のselfを必ず取るので,

self.variable = "hoge"

のように,いずれかのメソッドの中でインスタンス変数を追加した場合,他のメソッドの中でもself.variableを参照することができます.普通の関数との違いはこれだけです.

[問題]「hyper_beam」をメソッドとして実装しなさい.#

hyper_beamは「自分のmp \(\times\) 自分のattack - 相手のmp \(\times\) 相手のdefence」の分だけ相手にダメージを与える攻撃とします.

class Human:
    def __init__(self, hp=100,mp=100, attack=100, defence=100, speed=100):
        self.hp = hp
        self.mp = mp
        self.a = attack
        self.d = defence
        self.s = speed
    
    def megaton_punch(self, target):
        """
        targetはHumanインスタンス
        """
        damage = self.a - target.d
        if damage < 0:
            damage = 0
        
        target.hp -= damage
        return target 
    
    def megaton_kick(self, target):
        """
        targetはHumanインスタンス
        """
        damage = self.a - target.d/2
        if damage < 0:
            damage = 0
        
        target.hp -= damage
        return target 
    
ichiro = Human(mp=90, attack=110) 
jiro = Human(mp=95, defence=90) 

#ichiroからjiroへhyper_beam
print(jiro.hp)
100

クラスの継承#

人間クラスをもとにして,ドワーフクラスを作ります.ドワーフは人間と同様に,メガトンパンチ・メガトンキックや破壊光線を撃つことができます.しかしメガトンパンチの威力が人間より大きいことを表すために,種族ボーナスとして必ず+20ダメージになるようにしたいです.

このような場合に,クラスの「継承」を行います.継承とは,親のクラスの機能を引き継いだ子クラスを設計することです.定義の仕方は以下の通り.

class 子クラス(親クラス):
    def 親クラスになかったメソッド(self,...):
        ...
    
    def 親クラスと同名のメソッド(self, ...):
        親クラスとは違う機能

  1. 子クラスは基本的に,親クラスの全ての機能を継承します.同じ名前,同じ機能のメソッドならば改めて書く必要はありません.

  2. もし親クラスにないメソッドを追加したい場合は,普通にそのメソッドを定義してください.

  3. もし親クラスにあるメソッドの処理を変更したい場合は,同じメソッド名のまま普通に新しいメソッドとして定義してください.(これをオーバーライドと呼びます)

ではドワーフのクラスを作成します.

class Dwarf(Human):
    def __init__(self, hp=100,mp=100, attack=100, defence=100, speed=100):
        self.hp = hp
        self.mp = mp
        self.a = attack
        self.d = defence
        self.s = speed
    
    def megaton_punch(self, target):
        """
        targetはHumanインスタンス
        """
        damage = self.a - target.d
        if damage < 0:
            damage = 0
        
        target.hp -= damage + 20
        return target 
    
dwaichi = Dwarf(mp=90, attack=110) 
dwaji = Dwarf(mp=95, defence=90) 

dwaji=dwaichi.megaton_punch(dwaji)
print(dwaji.hp)
dwaji=dwaichi.megaton_kick(dwaji)
print(dwaji.hp)
60
-5.0

[問題]Humanクラスの子クラスとしてElfクラスを作成してください.#

このクラスは種族特性として,破壊光線が+20ダメージになりますが,その代わりにメガトンキックが-10ダメージになります.

class Elf():
    ...

クラス変数#

インスタンス変数self.variableは,インスタンス毎に値を変えることができました.これに対して,同じクラスから生成されれば,どのインスタンスであっても同じ値をとってほしい変数がある場合,クラス変数として定義します.

class Test:
    a = "クラス変数" # クラス変数はここに定義します.
    def __init__(self,x="インスタンス変数"):
        self.x = x

instance1 = Test("インスタンス変数1")
instance2 = Test("インスタンス変数2")

print(instance1.a)
print(instance1.x)

print(instance2.a)
print(instance2.x)
クラス変数
インスタンス変数1
クラス変数
インスタンス変数2

クラス変数はクラス.クラス変数に値を代入することで,更新することが可能です.更新するとどうなるか確認してみてください.

#Test.a = "クラス変数2"

print(instance1.a)
print(instance1.x)

print(instance2.a)
print(instance2.x)
クラス変数
インスタンス変数1
クラス変数
インスタンス変数2

クラス変数への代入を行うと,そのクラスから作られた全てのインスタンスが持つクラス変数も更新されます.

staticmethod#

staticmethodは,selfを受け取らないメソッドです.これは以下のような場合に使うと良いでしょう.

  1. 関数として定義しても良いけど,クラスの中に入れておいた方が収まりがいい場合

  2. 継承クラスで動作が変わらない時

実際に使う場合には,以下のように,「メソッドの宣言の前の行に@staticmethodをつける」ことで行います.

class Test2:
    a = "クラス変数" # クラス変数はここに定義します.
    def __init__(self,x="インスタンス変数"):
        self.x = x

    @staticmethod
    def test_static():
        print("適当な文字列")

    @staticmethod
    def test_static2(y):
        print(y)

instance3 = Test2()
instance3.test_static()
instance3.test_static2("適当")
適当な文字列
適当

メソッド,関数の上に@hogehogeと宣言することで関数の挙動を変えることができます.これを デコレータ と呼びます.@staticmethod以外にもたくさんのデコレータがありますし,自分でデコレータを定義することも可能です.

classmethod#

classmethodは,第一引数にインスタンス(self)の代わりにクラス自体(cls)を受け取るメソッドです.これは以下のような場合に使うと良いでしょう.

  1. クラス変数にだけアクセスする時

  2. 継承クラスで動作を変えたい時

実際に使う場合には,以下のように,「メソッドの宣言の前の行に@classmethodをつける」ことで行います.

class Test3:
    a = "クラス変数" # クラス変数はここに定義します.
    def __init__(self,x="インスタンス変数"):
        self.x = x

    @staticmethod
    def test_static():
        print("適当な文字列")

    @staticmethod
    def test_static2(y):
        print(y)

    @classmethod
    def test_classmethod(cls):
        print(cls)
        print(cls.a)
        print(cls.x)

    @classmethod
    def update_classvariable(cls, new_a):
        cls.a = new_a

    

instance4 = Test3()
instance5 = Test3()
instance4.test_classmethod()
<class '__main__.Test3'>
クラス変数
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[12], line 28
     26 instance4 = Test3()
     27 instance5 = Test3()
---> 28 instance4.test_classmethod()

Cell In[12], line 18, in Test3.test_classmethod(cls)
     16 print(cls)
     17 print(cls.a)
---> 18 print(cls.x)

AttributeError: type object 'Test3' has no attribute 'x'

clsはインスタンスではなくクラスが入っているため,インスタンス変数xを呼び出すことはできません.

また,cls.aに代入することで,クラス変数の値を更新することも可能です.

instance4.update_classvariable("新しい値")

print(instance4.a)
print(instance5.a)
新しい値
新しい値

[問題] 通常のメソッドの中でクラス変数を更新した場合,どのような挙動になるのか確認してみてください.#