Python / LESSON 13 / PY13

Python:クラスとオブジェクト指向

1学習の目的

2基礎解説

やりたいこと書き方意味
設計図を作るclass Item:クラス定義。名前は大文字始まり
初期化するdef __init__(self, name):インスタンス作成時に自動で呼ばれる
データを持つself.name = name属性。インスタンスごとに独立
実物を作るitem = Item("ノート")インスタンス化。self は書かない
✅ 覚えるべき重要ポイント
  1. クラスは「設計図」、インスタンスは「実物」。1つの設計図から実物をいくつでも作れる。
  2. __init__ はインスタンスを作った瞬間に自動で呼ばれる。呼び出し時に self書かないItem("ノート"))。
  3. メソッドの第1引数は必ず self。self はその実物自身を指し、self.name でそのインスタンスのデータを読み書きする。
  4. インスタンスはそれぞれ独立。a のデータを変えても b には影響しない。ここがモジュールとの決定的な違い。
  5. __str__ を定義すると print(オブジェクト) の表示を自分で決められる。デバッグや一覧表示が一気に楽になる。

現場使用例:商品・ユーザー・注文などデータのまとまり、銀行口座のような状態を持つもの、APIクライアント、ゲームのキャラクター。「名詞」で表せるものはクラスの候補

class Item:
    def __init__(self, name, price, qty):
        self.name = name
        self.price = price
        self.qty = qty

    def subtotal(self):
        return self.price * self.qty

    def __str__(self):
        return f"{self.name}:{self.subtotal():,}円"


item = Item("ノートPC", 128000, 2)

print(item.name)         # ノートPC
print(item.subtotal())   # 256000
print(item)              # ノートPC:256,000円

3基本ドリル(10問)

PY13-D01 名前を持つクラス Person を定義し、Person("田中") を作ってその名前を出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 田中

ヒント

__init__ の中で self.name = name と代入する。取り出しは p.name。

模範解答
class Person:
    def __init__(self, name):
        self.name = name


p = Person("田中")
print(p.name)
解説

クラスの最小形。__init__ で受け取った値を self に入れておくと、あとから属性として取り出せる。

PY13-D02 インスタンスを作ったときに自動で呼ばれるメソッドはどれか。
A. __main__ B. __init__ C. __str__ D. __name__
選択★☆☆無料
期待される結果

解答 == B

ヒント

「初期化」を意味する英単語の略。

模範解答
B
解説

init は initialize(初期化)の略。__str__ は print したときに呼ばれるもので、タイミングが違う。

PY13-D03 半径を持つ Circle クラスを定義し、面積を返すメソッド area を作れ。Circle(10) の面積を出力せよ(円周率は3.14とする)。 コーディング★☆☆無料
期待される結果

出力 == 314.0

ヒント

面積は 半径 ** 2 * 3.14。メソッドの中では self.r で半径を参照する。

模範解答
class Circle:
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2 * 3.14


c = Circle(10)
print(c.area())
解説

メソッドは引数を渡さなくても自分のデータを使えるのが利点。関数なら area(10) と半径を毎回渡す必要がある。

PY13-D04 クラスのメソッドの第1引数を完成させよ。 穴埋め★☆☆無料
コード
class Dog:
    def __init__(____, name):
        self.name = name


d = Dog("ポチ")
print(d.name)
期待される結果

出力 == ポチ

ヒント

「その実物自身」を指す、決まった名前の引数。

模範解答
self
解説

self という名前は慣習だが、Pythonのほぼ全てのコードがこれに従っている。変えてはいけないと考えてよい。

PY13-D05 メソッドの第1引数 self は何を指すか。
A. クラス自体 B. そのインスタンス自身 C. 親クラス D. 何も指さない
選択★☆☆無料
期待される結果

解答 == B

ヒント

設計図と実物、どちらを指しているか。

模範解答
B
解説

a.method() と書くと、Pythonが裏で a を self として渡している。だから呼び出し側では self を書かない。

PY13-D06 残高を持つ Account クラスを定義し、入金するメソッド deposit を作れ。残高1000で作成して500入金し、最終残高を出力せよ。 コーディング★★☆無料
期待される結果

出力 == 1500

ヒント

deposit の中で self.balance += amount とすれば属性を書き換えられる。

模範解答
class Account:
    def __init__(self, balance):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount


a = Account(1000)
a.deposit(500)
print(a.balance)
解説

メソッドで自分の状態を変えるのがクラスの本領。関数だと変更後の値を return して受け取り直す必要がある。

PY13-D07 print(オブジェクト) の表示内容を決めるメソッドを完成させよ。 穴埋め★★☆無料
コード
class Item:
    def __init__(self, name):
        self.name = name

    def ____(self):
        return f"商品:{self.name}"


print(Item("ノート"))
期待される結果

出力 == 商品:ノート

ヒント

文字列(string)に変換するときに呼ばれる特殊メソッド。前後にアンダースコア2つ。

模範解答
__str__
解説

定義しないと <__main__.Item object at 0x...> という読めない表示になる。一覧を出すクラスには必ず付けたい。

PY13-D08 a = Counter() と b = Counter() を作り、a の count を増やした場合、b の count はどうなるか。
A. 一緒に増える B. 影響を受けない C. エラーになる D. 0に戻る
選択★★☆無料
期待される結果

解答 == B

ヒント

同じ設計図から作った実物同士は、データを共有するのか。

模範解答
B
解説

インスタンスごとにデータは独立。だから商品を100個作っても、それぞれが自分の在庫数を持てる。

PY13-D09 商品名と価格を持つ Item クラスを定義し、ノート150円・ペン100円・消しゴム80円の3件をリストに入れて「ノート:150円」の形式で出力せよ。 コーディング★★☆無料
期待される結果

出力3行。ノート:150円 ほか

ヒント

リストの中に Item(...) を並べて作り、for item in items: で回す。

模範解答
class Item:
    def __init__(self, name, price):
        self.name = name
        self.price = price


items = [Item("ノート", 150), Item("ペン", 100), Item("消しゴム", 80)]

for item in items:
    print(f"{item.name}:{item.price}円")
解説

オブジェクトのリストは実務で最もよく使う形。PY06のタプルのリストより、名前でアクセスできるぶん読みやすい。

PY13-D10 単価と数量を持つ Item クラスに、小計を返す subtotal と、税込(10%・整数に切り捨て)を返す with_tax を定義せよ。with_tax は subtotal を使うこと。Item(1500, 3) の小計と税込を2行で出力せよ。 コーディング★★☆無料
期待される結果

出力 == 4500 / 4950(2行)

ヒント

メソッドの中から別のメソッドを呼ぶときも self.subtotal() と書く。

模範解答
class Item:
    def __init__(self, price, qty):
        self.price = price
        self.qty = qty

    def subtotal(self):
        return self.price * self.qty

    def with_tax(self):
        return int(self.subtotal() * 1.1)


item = Item(1500, 3)
print(item.subtotal())
print(item.with_tax())
解説

メソッド同士を組み合わせられる。計算の重複をクラスの中で解消できるので、小計の求め方が変わっても1箇所直せば済む。

4実践シナリオ(5問)

PY13-S01 【商品クラスで請求明細】商品名・単価・数量を持つ Item クラスに subtotal メソッドと __str__(「ノートPC:128,000円 × 1 = 128,000円」の形式)を定義せよ。ノートPC(128000,1)・マウス(3200,2)・USBメモリ(1500,3) の3件を出力し、最後に「合計:138,900円」と出力すること。 コーディング★★☆無料
期待される結果

出力4行が完全一致

ヒント

__str__ の中で self.subtotal() を呼べる。合計は sum(i.subtotal() for i in items) で求められる。

模範解答
class Item:
    def __init__(self, name, price, qty):
        self.name = name
        self.price = price
        self.qty = qty

    def subtotal(self):
        return self.price * self.qty

    def __str__(self):
        return f"{self.name}:{self.price:,}円 × {self.qty} = {self.subtotal():,}円"


items = [
    Item("ノートPC", 128000, 1),
    Item("マウス", 3200, 2),
    Item("USBメモリ", 1500, 3),
]

for item in items:
    print(item)

print(f"合計:{sum(i.subtotal() for i in items):,}円")
解説

PY08・PY12と同じ請求書だが、表示形式がクラスの中に閉じ込められた。明細の見た目を変えたければ __str__ だけを直せばよい。

PY13-S02 【学生クラス】名前と点数リストを持つ Student クラスに、平均を返す average と、平均60以上なら「合格」を返す judge を定義せよ。田中[82, 91, 67, 76] と鈴木[45, 60, 38, 67] について「田中:平均79.0点 → 合格」の形式で2行出力すること。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

judge の中で self.average() を呼んで判定する。平均は {x:.1f} で小数1桁表示。

模範解答
class Student:
    def __init__(self, name, scores):
        self.name = name
        self.scores = scores

    def average(self):
        return sum(self.scores) / len(self.scores)

    def judge(self):
        if self.average() >= 60:
            return "合格"
        return "不合格"


students = [
    Student("田中", [82, 91, 67, 76]),
    Student("鈴木", [45, 60, 38, 67]),
]

for s in students:
    print(f"{s.name}:平均{s.average():.1f}点 → {s.judge()}")
解説

属性にリストを持たせられるのがポイント。名前と点数が1つのオブジェクトにまとまるので、PY06の並行リストのような順序ズレは原理的に起きない。

PY13-S03 【銀行口座】残高を持つ Account クラスに deposit(入金)と withdraw(出金)を定義せよ。残高不足の出金は raise ValueError("残高が不足しています") とすること。残高10,000で作り、5,000入金 → 3,000出金 → 999,999出金(失敗)を行い、「入金後:15,000円」「出金後:12,000円」「エラー:残高が不足しています」「最終残高:12,000円」の4行を出力せよ。 コーディング★★☆無料
期待される結果

出力4行が完全一致

ヒント

失敗する出金は try / except ValueError as e: で受け止める。PY10の例外処理をそのまま使う。

模範解答
class Account:
    def __init__(self, balance=0):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("残高が不足しています")
        self.balance -= amount


account = Account(10000)

account.deposit(5000)
print(f"入金後:{account.balance:,}円")

account.withdraw(3000)
print(f"出金後:{account.balance:,}円")

try:
    account.withdraw(999999)
except ValueError as e:
    print(f"エラー:{e}")

print(f"最終残高:{account.balance:,}円")
解説

不正な操作をクラス自身が拒否する設計。残高チェックを使う側に任せると書き忘れが起きるが、メソッドの中で守れば絶対に破られない。失敗しても残高が変わっていない点も重要。

PY13-S04 【インスタンスの独立性】カウンタを持つ Counter クラスに increment と reset を定義せよ。a と b の2つを作り、a を3回・b を1回増やして「a: 3 / b: 1」と出力し、a だけリセットして「リセット後 a: 0 / b: 1」と出力すること。 コーディング★★★無料
期待される結果

出力2行が完全一致

ヒント

__init__ の中で self.count = 0 と初期化する。a.reset() を呼んでも b は変わらない。

模範解答
class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

    def reset(self):
        self.count = 0


a = Counter()
b = Counter()

a.increment()
a.increment()
a.increment()
b.increment()

print(f"a: {a.count} / b: {b.count}")

a.reset()
print(f"リセット後 a: {a.count} / b: {b.count}")
解説

b が影響を受けないことがこの問題の答え。同じクラスから作っても実物は別物。PY12のモジュールは全体で1つしか存在しないので、ここが両者の使い分けの分かれ目になる。

PY13-S05 【在庫管理クラス】商品名・在庫数・発注閾値(既定10)を持つ Product クラスに、閾値未満か判定する is_low、補充する restock、そして __str__(「ノート:8個 ⚠発注」形式。閾値以上なら記号なし)を定義せよ。
ボールペン120・ノート8・ファイル3 の3件を出力し、「--- 補充後 ---」を挟んで、要発注の商品だけ50個補充して「ノート:58個」の形式で出力すること。
コーディング★★★無料
期待される結果

出力6行が完全一致

ヒント

__str__ の中で三項演算子を使い、is_low() の結果で記号を切り替える。補充ループでは if p.is_low(): で絞る。

模範解答
class Product:
    def __init__(self, name, stock, threshold=10):
        self.name = name
        self.stock = stock
        self.threshold = threshold

    def is_low(self):
        return self.stock < self.threshold

    def restock(self, amount):
        self.stock += amount

    def __str__(self):
        mark = " ⚠発注" if self.is_low() else ""
        return f"{self.name}:{self.stock}個{mark}"


products = [
    Product("ボールペン", 120),
    Product("ノート", 8),
    Product("ファイル", 3),
]

for p in products:
    print(p)

print("--- 補充後 ---")

for p in products:
    if p.is_low():
        p.restock(50)
        print(f"{p.name}:{p.stock}個")
解説

PY06・PY07・PY09で何度も書いた在庫チェックの最終形。判定ロジックも表示形式も商品自身が持っているので、使う側は p.is_low() と print(p) を呼ぶだけでよくなった。

5仕上げ課題

PY13-FINAL 【図書貸出システム】2つのクラスを設計して、貸出管理を実装せよ。

Book クラス:属性 title / author / is_borrowed(初期値 False)
 ・borrow() … 貸出中なら raise ValueError("「タイトル」は貸出中です")、そうでなければ貸出中にする
 ・give_back() … 返却状態に戻す
 ・__str__ … 「[貸出可] Python入門 / 山田太郎」の形式(貸出中なら [貸出中])

Library クラス:属性 name / books(空リストで開始)
 ・add(book) … 蔵書に追加
 ・find(title) … タイトルで探して Book を返す(無ければ None)
 ・available_count() … 貸出可能な冊数を返す

処理:「ゼロカラ図書館」に3冊を追加して一覧表示 → ["Python入門", "Web開発入門", "Python入門"] の順に貸出(3回目は失敗する)→ 再度一覧表示と集計。

出力仕様(13行):
=== ゼロカラ図書館 ===
[貸出可] Python入門 / 山田太郎
[貸出可] データ分析の基礎 / 佐藤花子
[貸出可] Web開発入門 / 鈴木一郎
---
「Python入門」を貸し出しました
「Web開発入門」を貸し出しました
エラー:「Python入門」は貸出中です
---
[貸出中] Python入門 / 山田太郎
[貸出可] データ分析の基礎 / 佐藤花子
[貸出中] Web開発入門 / 鈴木一郎
貸出可能:1冊 / 全3冊
期待される結果

出力13行が完全一致

ヒント

Library の books は __init__ の中で self.books = [] と初期化する。find は for で回して title が一致した Book を return、見つからなければ最後に return None。available_count は内包表記で [b for b in self.books if not b.is_borrowed] の件数を数えるとよい。貸出は try / except ValueError as e: で受け止める。

模範解答
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.is_borrowed = False

    def borrow(self):
        if self.is_borrowed:
            raise ValueError(f"「{self.title}」は貸出中です")
        self.is_borrowed = True

    def give_back(self):
        self.is_borrowed = False

    def __str__(self):
        status = "貸出中" if self.is_borrowed else "貸出可"
        return f"[{status}] {self.title} / {self.author}"


class Library:
    def __init__(self, name):
        self.name = name
        self.books = []

    def add(self, book):
        self.books.append(book)

    def find(self, title):
        for book in self.books:
            if book.title == title:
                return book
        return None

    def available_count(self):
        return len([b for b in self.books if not b.is_borrowed])


library = Library("ゼロカラ図書館")
library.add(Book("Python入門", "山田太郎"))
library.add(Book("データ分析の基礎", "佐藤花子"))
library.add(Book("Web開発入門", "鈴木一郎"))

print(f"=== {library.name} ===")
for book in library.books:
    print(book)

print("---")

for title in ["Python入門", "Web開発入門", "Python入門"]:
    book = library.find(title)

    try:
        book.borrow()
        print(f"「{title}」を貸し出しました")
    except ValueError as e:
        print(f"エラー:{e}")

print("---")
for book in library.books:
    print(book)

print(f"貸出可能:{library.available_count()}冊 / 全{len(library.books)}冊")
解説

クラスがクラスを持つ(Library が Book のリストを持つ)構造が書けたことが最大の到達点。実務のシステムはほぼこの形で、ユーザーが注文を持ち、注文が商品を持つ、といった入れ子で組み立てられている。貸出可否の判定は Book 自身が、蔵書の管理は Library が担当する——それぞれのクラスが自分の責任範囲だけを知っている状態が、オブジェクト指向設計の目指すゴール。