第7回:メソッド¶
前回作ったクラスは「データを持つだけ」だった。今回は、クラスに動きをつけるメソッドを学ぶ。例えばゲームのキャラなら、「攻撃する」「ダメージを受ける」「ステータスを表示する」といった動きをクラスに持たせられる。
メソッドとは¶
メソッド: インスタンスが行う動きをまとめたもの。クラスの中に書く、関数のような存在。
- 第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.メソッド名() の形は、すべて str や list クラスが持っているメソッドを呼び出している。
>>> 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()と呼ぶと、selfにaが自動で入る- だから同じメソッドでも、
aを呼べば勇者、bを呼べば魔法使いの結果になる - 呼び出し側では
selfを書かない(自動で渡される)
プレ演習7-1¶
上の Character クラスを動かそう。さらにインスタンス c = Character('盗賊', 70) を作って、c.show_status() も呼んでみよう。
期待される実行結果:
メソッドに引数を渡す¶
メソッドも関数のように引数を受け取れる。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にならない)
メソッドはデータの門番
- 「こういう使い方しか許さない」を実装できる
- データ(インスタンス変数)を守る門番の役割を果たす
- これがクラス+メソッドの本当の強み
プレ演習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に止まるaとbは別インスタンスだから 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回復
期待される実行結果:
プレ演習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
期待される実行結果:
ヒント
- メソッド定義の第1引数はなんだった?
- インスタンス変数を参照するときの書き方は?
- メソッドを「呼び出す」のと「参照する」のは違う
プレ演習7-5¶
HP を 10 で割った数だけ ■ を表示する show_hp_bar メソッドを書こう。
ヒント:
'■' * 3は'■■■'になる(文字列の掛け算)//は整数の割り算(あまり切り捨て)。70 // 10は7
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(ほかのファイル):
__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)
期待される実行結果:
基本課題¶
次のインスタンス変数とメソッドを持つクラス MyClass を作成せよ。そのクラスから 2 つのインスタンスを生成し、インスタンスの各値を設定したうえで、print_info() メソッドを呼び出すことで実行例のように出力するプログラムを作成せよ。
| 種別 | 名前 | 内容 | 型 |
|---|---|---|---|
| インスタンス変数 | num |
1桁の番号 | 整数 |
| インスタンス変数 | name |
名前 | 文字列 |
| インスタンス変数 | type |
血液型 | 文字列 |
| メソッド | print_info() |
番号・名前・血液型を出力 | — |
実行例:
応用課題¶
次のインスタンス変数とメソッドを持つクラス MyBunsu を作成せよ。また、キーボードから分子と分母を入力し、MyBunsu クラスを用いて、実行例のとおり適切な分数の形で出力するプログラムを作成せよ。ただし外部モジュールのインポートは行わず、組み込み関数も使用しないこと。
MyBunsu クラス:
| 種別 | 名前 | 内容 |
|---|---|---|
| インスタンス変数 | bunshi |
分子 |
| インスタンス変数 | bunbo |
分母 |
| メソッド | gcd() |
約分を行う |
| メソッド | print_display() |
分数を適切に表示する |
実行例:
分子を整数で入力して下さい: 12
分母を整数で入力して下さい: 18
2/3
分子を整数で入力して下さい: 9
分母を整数で入力して下さい: 3
3
分子を整数で入力して下さい: 1
分母を整数で入力して下さい: -3
-1/3
ヒント:
- 約分にはユークリッドの互除法が使える(2 数の最大公約数を求めるアルゴリズム)
- 分母が 1 になったら整数として表示する
- 分母がマイナスなら、符号を分子側に寄せる