コンテンツにスキップ

第7回:メソッド

前回作ったクラスは「データを持つだけ」だった。今回は、クラスに動きをつけるメソッドを学ぶ。例えばゲームのキャラなら、「攻撃する」「ダメージを受ける」「ステータスを表示する」といった動きをクラスに持たせられる。

メソッドとは

メソッド: インスタンスが行う動きをまとめたもの。クラスの中に書く、関数のような存在。

def メソッド名(self, その他の引数):
    処理内容
  • 第1引数は必ず self(自分自身を表す)
  • 関数のように def で定義するが、必ずクラスの中に書く

メソッドの例:HP を表示する

class Character:
    def __init__(self, name, hp):
        self.name = name      # ┐ インスタンス変数
        self.hp = hp          # ┘

    def show_status(self):                          # ← メソッド(動き)
        print(self.name, 'のHP:', self.hp)

a = Character('勇者', 100)
b = Character('魔法使い', 50)

a.show_status()   # → 勇者 のHP: 100
b.show_status()   # → 魔法使い のHP: 50

インスタンス.メソッド名() の形で呼び出す。

メソッドはすでに見たことがある

実は、str.upper()list.append() のような xx.メソッド名() の形は、すべて strlist クラスが持っているメソッドを呼び出している。

>>> name = 'hello'
>>> name.upper()      # ← str クラスのメソッド
'HELLO'

>>> nums = [1, 2, 3]
>>> nums.append(4)    # ← list クラスのメソッド
>>> nums
[1, 2, 3, 4]

今回は、それを自分のクラス(Character)でも作れるようになる。

メソッド呼び出しと self の動き

self は「今操作しているインスタンス自身」への参照。

a.show_status()   # 内部では show_status(self=a) として呼ばれる
                  # → self.name は a.name = '勇者'
b.show_status()   # 内部では show_status(self=b) として呼ばれる
                  # → self.name は b.name = '魔法使い'
  • a.show_status() と呼ぶと、selfa が自動で入る
  • だから同じメソッドでも、a を呼べば勇者、b を呼べば魔法使いの結果になる
  • 呼び出し側では self を書かない(自動で渡される)

プレ演習7-1

上の Character クラスを動かそう。さらにインスタンス c = Character('盗賊', 70) を作って、c.show_status() も呼んでみよう。

期待される実行結果:

勇者 のHP: 100
魔法使い のHP: 50
盗賊 のHP: 70

メソッドに引数を渡す

メソッドも関数のように引数を受け取れる。self 以降に書く。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def take_damage(self, damage):              # ← damage を受け取る
        self.hp = self.hp - damage
        print(self.name, 'の残りHP:', self.hp)

a = Character('勇者', 100)
a.take_damage(30)   # → 勇者 の残りHP: 70
a.take_damage(50)   # → 勇者 の残りHP: 20

メソッドの中で「制約」をかける

前回の「型を作ると制約を組み込める」が、ここで本当に効いてくる。HP がマイナスにならないよう、メソッド内で制約を実装できる。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def take_damage(self, damage):
        self.hp = self.hp - damage
        if self.hp < 0:        # ← 制約:0未満になったら
            self.hp = 0        #    0で止める
        print(self.name, 'の残りHP:', self.hp)

a = Character('勇者', 100)
a.take_damage(70)   # → 勇者 の残りHP: 30
a.take_damage(50)   # → 勇者 の残りHP: 0  (-20にならない)

メソッドはデータの門番

  1. 「こういう使い方しか許さない」を実装できる
  2. データ(インスタンス変数)を守る門番の役割を果たす
  3. これがクラス+メソッドの本当の強み

プレ演習7-2

以下のコードを実行する前に、出力を紙やメモ帳に書き出してみよう。その後実行して、自分の予測と合っているか確認しよう。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def take_damage(self, damage):
        self.hp = self.hp - damage
        if self.hp < 0:
            self.hp = 0
        print(self.name, 'の残りHP:', self.hp)

a = Character('勇者', 100)
b = Character('魔法使い', 50)

a.take_damage(30)
b.take_damage(60)
a.take_damage(80)
ポイント
  • b.take_damage(60)50-60=-10 になっても、制約で 0 に止まる
  • ab は別インスタンスだから HP は独立。a を攻撃しても b の HP は変わらない

プレ演習7-3

次のコードの空欄(①②)を埋めて、正しく動くようにしよう。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def heal(self, amount):                     # 回復するメソッド
        ._____ = self.hp + amount              # ① HPを増やす
        print(self.name, 'の残りHP:', self.hp)

a = Character('勇者', 30)
a._____(50)                                    # ② heal を呼ぶ、50回復

期待される実行結果:

勇者 の残りHP: 80

プレ演習7-4

次のコードには 3か所の誤り がある。それぞれ見つけて、正しく動くように直そう。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def show_status():
        print(name, 'のHP:', self.hp)

a = Character('勇者', 100)
a.show_status

期待される実行結果:

勇者 のHP: 100
ヒント
  • メソッド定義の第1引数はなんだった?
  • インスタンス変数を参照するときの書き方は?
  • メソッドを「呼び出す」のと「参照する」のは違う

プレ演習7-5

HP を 10 で割った数だけ を表示する show_hp_bar メソッドを書こう。

ヒント:

  • '■' * 3'■■■' になる(文字列の掛け算)
  • // は整数の割り算(あまり切り捨て)。70 // 107
class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def take_damage(self, damage):
        self.hp = self.hp - damage
        if self.hp < 0:
            self.hp = 0

    # ここに show_hp_bar メソッドを書く

a = Character('勇者', 100)
a.show_hp_bar()      # → 勇者 ■■■■■■■■■■
a.take_damage(30)
a.show_hp_bar()      # → 勇者 ■■■■■■■

発展:他インスタンスのメソッドを呼ぶ

メソッドの引数に別のインスタンスを渡せば、そのインスタンスのメソッドを呼べる。これで「勇者が魔王を攻撃する」のような相互作用が書ける。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def take_damage(self, damage):
        self.hp = self.hp - damage
        if self.hp < 0:
            self.hp = 0
        print(self.name, 'の残りHP:', self.hp)

    def attack(self, other):
        print(self.name, 'の攻撃!')
        other.take_damage(30)        # ← 相手の take_damage を呼ぶ

a = Character('勇者', 100)
b = Character('魔王', 200)
a.attack(b)        # 勇者が魔王を攻撃 → 魔王の残りHP: 170

クラスメソッド

クラスメソッド: インスタンスを生成しなくても クラス名.メソッド名() で実行できるメソッド。第1引数は自分のクラスを表す cls

class Character:
    count = 0                          # クラス変数:作られたキャラの総数

    def __init__(self, name, hp):
        self.name = name
        self.hp = hp
        Character.count += 1           # 作られるたびに +1

    @classmethod                       # ← これに続くメソッドがクラスメソッド
    def show_count(cls):               # ← 第1引数は cls(クラス自身)
        print('キャラ総数:', cls.count)

a = Character('勇者', 100)
b = Character('魔法使い', 50)
Character.show_count()                 # → キャラ総数: 2
  • インスタンスごとの状態ではなく、クラス全体に関する処理に使う
  • @classmethod というデコレータでクラスメソッドと宣言する
  • 呼び出すときも Character.show_count() のようにクラス名から呼べる

オリジナルのクラスをモジュールとして利用する

クラスを定義したファイルを、別ファイルからモジュールとして利用できる。

character.py(クラス定義したファイル):

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def show_status(self):
        print(self.name, 'のHP:', self.hp)

main.py(ほかのファイル):

from character import Character
#    ↑拡張子以外のファイル名  ↑クラス名

a = Character('勇者', 100)
a.show_status()

__name__ 変数

character.py をそのまま import すると、ファイルの一番下に書いたテスト用のコードまで実行されてしまう。

class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

# ↓ import しただけで、以下も動いてしまう
a = Character('勇者', 100)
print(a.name)

__name__ 変数の値で区別できる:

  • プログラムが直接実行された → '__main__'
  • import 経由 → モジュール名(この場合は 'character'
class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

# ↓ 直接実行のときだけ動く
if __name__ == '__main__':
    a = Character('勇者', 100)
    print(a.name)

これでテストコードは直接実行のときだけ動き、import したときには動かなくなる。


授業内演習

次のプログラムにおいて、インスタンスの内容を出力するメソッド print_info()MyPassport クラスに追加し、print_info() メソッドを呼び出すことで、MyPassport クラスのインスタンスの値を出力せよ。

class MyPassport:
    def __init__(self, name, nen, tsuki, hi, number):
        print('パスポートを作ります。')
        self.name = name
        self.nen = nen
        self.tsuki = tsuki
        self.hi = hi
        self.number = number

# インスタンス作成時に必要な引数を渡す
a = MyPassport('Jindai Tarou', 2015, 5, 27, 'JP12345')

# インスタンス変数の表示
print('氏名:', a.name)
print('生まれ年:', a.nen)
print('生まれ月:', a.tsuki)
print('生まれ日:', a.hi)
print('パスポート番号:', a.number)

期待される実行結果:

パスポートを作ります。
氏名: Jindai Tarou
生まれ年: 2015
生まれ月: 5
生まれ日: 27
パスポート番号: JP12345

基本課題

次のインスタンス変数とメソッドを持つクラス MyClass を作成せよ。そのクラスから 2 つのインスタンスを生成し、インスタンスの各値を設定したうえで、print_info() メソッドを呼び出すことで実行例のように出力するプログラムを作成せよ。

種別 名前 内容
インスタンス変数 num 1桁の番号 整数
インスタンス変数 name 名前 文字列
インスタンス変数 type 血液型 文字列
メソッド print_info() 番号・名前・血液型を出力

実行例:

番号: 7, 名前: 佐藤, 血液型: AB
番号: 3, 名前: 田中, 血液型: O

応用課題

次のインスタンス変数とメソッドを持つクラス MyBunsu を作成せよ。また、キーボードから分子と分母を入力し、MyBunsu クラスを用いて、実行例のとおり適切な分数の形で出力するプログラムを作成せよ。ただし外部モジュールのインポートは行わず、組み込み関数も使用しないこと

MyBunsu クラス:

種別 名前 内容
インスタンス変数 bunshi 分子
インスタンス変数 bunbo 分母
メソッド gcd() 約分を行う
メソッド print_display() 分数を適切に表示する

実行例:

分子を整数で入力して下さい: 12
分母を整数で入力して下さい: 18
2/3
分子を整数で入力して下さい: 9
分母を整数で入力して下さい: 3
3
分子を整数で入力して下さい: 1
分母を整数で入力して下さい: -3
-1/3

ヒント:

  • 約分にはユークリッドの互除法が使える(2 数の最大公約数を求めるアルゴリズム)
  • 分母が 1 になったら整数として表示する
  • 分母がマイナスなら、符号を分子側に寄せる