コンテンツにスキップ

第6回:クラス

ゲームのキャラクタは「役職・HP・MP・攻撃・防御」のように、同じ項目をまとめて持つ。これを変数だけでやろうとすると role1, hp1, mp1, ..., role2, hp2, ... とひたすら並べることになり、人数が増えると破綻する。これを綺麗に扱う道具がクラス

クラスとは

ひとことで言うと、データの「型」を作る道具

「キャラ」という型を新しく作り、その中に「役職・HP・MP・攻撃・防御」を持たせると決めておく。これがクラス = 設計図。実際のキャラ(勇者、魔法使い)はこの設計図から作る。

「型」を作ると「制約」を組み込める

データをまとめるだけが目的ではなく、おかしな使い方を最初から弾けるのが本当の強み。

  • キャラの HP: 文字列 'たくさん' を弾く、上限100・下限0にする
  • 役職: 自由入力だと「勇者」「ゆうしゃ」「Hero」が混ざる → 選択肢から選ぶ形式(enum)にする

「こういう使い方しか許さない」が組み込めるのが型・クラスの強み。プログラムが大きくなるほど、バグを防ぐ武器になる。

クラスとインスタンス

  • クラス(設計図): どんなデータを持つかの定義。1つ書けばよい
  • インスタンス(具体物): 設計図から作った実体。いくつでも作れ、それぞれ独立した値を持つ
[クラス Character]        →   [インスタンス a]      [インスタンス b]
  役職 / HP / MP /             役職='勇者'           役職='魔法使い'
  攻撃 / 防御                   HP=100 ...            HP=50 ...

クラスの定義(最小構文)

class MyStudent:
    pass        # 「何も書かない」の合図(処理を飛ばす)

# クラスからインスタンスを作る
a = MyStudent()
print(a)
# → <__main__.MyStudent object at 0x7f...>
  • class キーワードでクラスを定義
  • MyStudent() でインスタンスを生成
  • <...object at 0x...> の表示は今は気にしなくてよい(インスタンスがメモリ上のどこにあるかの情報)

プレ演習6-1

上のコードを打ち込んで動かし、もう1つインスタンス b を作って、ab の出力を見比べよう。

# 追加
b = MyStudent()
print(b)

インスタンス変数① あとから代入

インスタンス.変数名 = 値 でインスタンス変数を持たせ、インスタンス.変数名 で参照する。

class MyStudent:
    pass

a = MyStudent()
a.name = '田中太郎'   # ドットでつないで代入
a.age = 20

print(a.name)   # → 田中太郎
print(a.age)    # → 20

ただしこの方法だと、インスタンスを作るたびに毎回手で代入が必要。

プレ演習6-2

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

class MyStudent:
    pass

a = MyStudent()
a.name = '田中'
a.age = 20

b = MyStudent()
b.name = '佐藤'
b.age = 19

print(a.name, a.age)
print(b.name, b.age)
a.age = 21
print(a.age)
print(b.age)

インスタンス変数② __init__ で初期化

毎回手で代入するのは面倒。インスタンス生成時に自動で初期値が入る仕組みが初期化メソッド __init__

class MyStudent:
    def __init__(self):
        self.name = ''
        self.age = 0

a = MyStudent()
print(a.name)   # → (空文字列)
print(a.age)    # → 0

アンダーバーの数に注意

__init__ は前後ともアンダーバー2つ_init_(1つ)では初期化メソッドとして認識されない。__ はPythonが特別扱いするメソッドの印。

プレ演習6-3

上の __init__name='', age=0 に初期化するコードを打ち込んで動かし、そのあとインスタンス aname'田中' に書き換えて print(a.name) で表示してみよう。

__init__ に引数を渡す

__init__ は引数を受け取れる。MyStudent('田中', 20) のように引数つきで呼ぶと、インスタンスを作るときに必要な情報を一気に渡せる。

class MyStudent:
    def __init__(self, name, age):
        self.name = name
        self.age = age

a = MyStudent('田中', 20)   # ここで引数を渡す
b = MyStudent('佐藤', 19)   # self 部分は不要
print(a.name, a.age)   # → 田中 20
print(b.name, b.age)   # → 佐藤 19

self とは何か

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

class MyStudent:
    def __init__(self, name):
        self.name = name

a = MyStudent('田中')   # この呼び出しは…

a = MyStudent('田中') のとき、内部的には __init__(self=a, name='田中') と処理される。つまり self.name = namea.name = '田中' と同じ意味になる。

self は自動で渡される

メソッド定義では第1引数に self を書くが、呼び出すときは書かない(MyStudent('田中')self には自動的にそのインスタンスが入る)。

プレ演習6-4

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

class MyStudent:
    def _init_(name, age):
        self.name = name
        age = age

a = MyStudent('田中', 20)
print(a.name)
print(a.age)

期待される実行結果:

田中
20
ヒント

これまでに出てきた「__init__ の書き方」「self の役割」「インスタンス変数の代入のしかた」を1つずつ照らし合わせてみよう。

プレ演習6-5

次のインスタンス変数を持つクラス Item を作り、以下のコードが正しく動くようにしよう。

  • name(商品名、文字列)
  • price(価格、整数)
# これより上に Item クラスの定義を書く

ringo = Item('りんご', 150)
banana = Item('バナナ', 100)

print(ringo.name, 'は', ringo.price, '円')
print(banana.name, 'は', banana.price, '円')

期待される実行結果:

りんご は 150 円
バナナ は 100 円

余裕のある人向け: weight(重さ、整数)も追加し、インスタンスを3つ以上作ってみよう。

クラス変数とインスタンス変数

  • インスタンス変数: インスタンスごとに別々の値(例: name
  • クラス変数: クラスで全インスタンス共通の値(例: school
class MyStudent:
    school = '神奈川大学'         # クラス変数(全員共通)

    def __init__(self, name):
        self.name = name          # インスタンス変数(個別)

a = MyStudent('田中')
b = MyStudent('佐藤')
print(a.name, a.school)            # → 田中 神奈川大学
print(b.name, MyStudent.school)    # → 佐藤 神奈川大学

クラス変数は インスタンス.変数名 でも クラス名.変数名 でもアクセスできる。全員共通の情報や、インスタンスごとに変わらない情報に使う。


授業内演習

問1(動作確認)

次のプログラムの動作を確認しなさい。

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

a = MyPassport()
a.name = 'Jindai Tarou'
a.nen = 2015
a.tsuki = 5
a.hi = 27
a.number = 'JP12345'
print('氏名:', a.name)
print('生まれ年:', a.nen)
print('生まれ月:', a.tsuki)
print('生まれ日:', a.hi)
print('パスポート番号:', a.number)

問2

問1のプログラムで、インスタンス生成時にインスタンス変数の値を設定するようにプログラムを変更しなさい。出力は問1と同じになること。

  • ヒント: __init__ に引数を追加する

問3

問2のプログラムに、クラス変数 school = '神奈川大学' を追加せよ。また2つのインスタンスを生成し、それぞれの氏名と所属を出力するプログラムに変更しなさい。

基本課題

次のインスタンス変数を持つクラス MyID を作成し、そのクラスから1つインスタンスを生成して各値を設定したうえで出力するプログラムを作成せよ。

変数名 内容
num 整理番号 整数
name 名前 文字列
type 血液型 文字列

約束ごと:

  • インスタンス変数の名前は num, name, type を使うこと
  • __init__ で3つのインスタンス変数を初期化すること(後から代入はしない)

出力例:

整理番号:1
名前:田中太郎
血液型:A

応用課題

次のクラス MyStudent を作成し、そのクラスから2つ以上のインスタンスを生成して各値を設定したうえで出力するプログラムを作成せよ。

種別 変数名 内容
インスタンス変数 num 学生証番号
インスタンス変数 name 名前
インスタンス変数 GPA GPA
クラス変数 school 出身校

約束ごと:

  • クラス内では __init__ 以外のメソッドの定義は行わないこと
  • __init__ で3つのインスタンス変数を初期化すること(後から代入しないこと)

出力例:

学生証番号:1001  名前:田中太郎  GPA:3.2  出身校:神奈川高校
学生証番号:1002  名前:佐藤花子  GPA:3.8  出身校:神奈川高校