閉じる

【Python】クラスの属性とメソッドとは?インスタンス変数の基本

Pythonのクラスでは、データを入れる箱としての属性(インスタンス変数)と、そのデータを使って処理を行うメソッドを組み合わせて設計します。

属性はオブジェクトが持つ状態、メソッドはその状態に基づくふるまいです。

本記事では、初心者がつまずきやすいポイントを避けつつ、selfの意味、属性の定義とアクセス、メソッドの書き方までを段階的に解説します。

クラスの属性(インスタンス変数)とメソッドの基本

属性(インスタンス変数)とは

属性(インスタンス変数)は、生成したオブジェクトごとに値が異なるデータを指します。

例えばユーザーなら名前や年齢、口座なら残高などが該当します。

同じクラスから作ったオブジェクトでも、属性の中身はオブジェクトごとに独立しています。

属性はクラス定義内で初期化するのが基本で、各オブジェクトの「状態」を表現します。

次の観点で理解しておくと整理しやすいです。

  • 属性は「値」そのものを保持します。
  • プログラムからはobj.attrというドット記法で参照します。
  • 値は実行中に更新できます。

以下の表は、属性とメソッドの違いを簡潔にまとめたものです。

観点属性(インスタンス変数)メソッド
役割状態やデータを保持データに基づく処理を提供
nameagebalancegreet()deposit()
呼び出し方obj.attrobj.method()
変更代入で変更実行で状態を変える場合がある

メソッドとは

メソッドはクラス内に定義する関数で、特定のオブジェクトの文脈で動作します。

インスタンスメソッドの第1引数は慣例的にselfと書き、そのオブジェクト自身を受け取ります

これにより、そのオブジェクトの属性を読み書きできるようになります。

処理のまとまりをメソッドとして定義することで、コードの再利用性と可読性が向上します。

selfが指すものと属性・メソッドの関係

selfはメソッドが呼ばれた「そのオブジェクト自身」を示します。

実際にid(self)id(obj)を比べると同一であることがわかります。

つまりメソッドの内部からself.attrとしてアクセスすると、そのオブジェクトが持つ属性に触れていることになります。

Python
# selfがオブジェクト自身を指すことを確認する例
class Sample:
    def whoami(self):
        # メソッドの中ではselfが「呼び出し元のオブジェクト」
        print("id(self) =", id(self))

obj = Sample()
print("id(obj)  =", id(obj))
obj.whoami()  # whoami内のselfはobjを指す
実行結果
id(obj)  = 140352541234000
id(self) = 140352541234000

インスタンス変数の定義・初期化とアクセス

__init__で属性を定義する基本

属性は通常、コンストラクタ__init__で初期化します。

ここでself.attr = 値と代入すると、そのオブジェクトに属性が作られます

Python
# Userクラスの基本的な属性定義
class User:
    def __init__(self, name, age):
        # ここでインスタンス属性を初期化
        self.name = name
        self.age = age

u = User("Alice", 20)
print(u.name)  # "Alice"
print(u.age)   # 20
実行結果
Alice
20
注意点

__init__で必須の属性は必ず初期化してください。初期化を忘れると、後でアクセスした際に例外が発生します。

ドット記法での読み書き(obj.attr)

属性はobj.attrで読み取り、代入で書き換えます。

Pythonでは柔軟に属性を更新できます。

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

u = User("Alice", 20)
print(u.age)    # 読み取り
u.age = 21      # 書き換え
print(u.age)    # 21に更新されている
実行結果
20
21

メソッド内での更新(self.attr = 値)

メソッドはself経由で属性を更新できます。

状態の変化をメソッドに閉じ込めると、使う側は意図が明確になり、バグを減らせます。

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

    def have_birthday(self):
        # インスタンスの状態をメソッドから更新
        self.age += 1
        print(f"Happy birthday, {self.name}! You are now {self.age}.")

u = User("Alice", 20)
u.have_birthday()
print(u.age)  # 21
実行結果
Happy birthday, Alice! You are now 21.
21

存在しない属性で起きるAttributeError

定義していない属性にアクセスするとAttributeErrorが発生します。

綴りの誤りにも注意が必要です。

Python
class User:
    def __init__(self, name):
        self.name = name

u = User("Alice")
print(u.name)
print(u.height)  # 定義していない属性。例外が発生する
実行結果
Alice
Traceback (most recent call last):
  File "example.py", line 8, in <module>
    print(u.height)
AttributeError: 'User' object has no attribute 'height'

インスタンスメソッドの定義・呼び出し

インスタンスメソッドの書き方(def method(self))

インスタンスメソッドはdefで定義し、selfを第1引数に取ります。

これによりself.attrを介して属性へアクセスできます。

Python
class Greeter:
    def __init__(self, name):
        self.name = name

    def greet(self):  # 第1引数は必ずself
        return f"Hello, {self.name}!"

g = Greeter("Bob")
print(g.greet())
実行結果
Hello, Bob!

引数と戻り値の扱い

メソッドは通常の関数と同様に、引数を受け取り、値を返せます。

Python
class Person:
    def __init__(self, first, last):
        self.first = first
        self.last = last

    def full_name(self, title=None):
        # 引数で出力形式を変え、文字列を戻り値として返す
        name = f"{self.first} {self.last}"
        if title:
            return f"{title} {name}"
        return name

p = Person("Carol", "Smith")
print(p.full_name())           # 引数なし
print(p.full_name("Dr."))      # 引数あり
実行結果
Carol Smith
Dr. Carol Smith

メソッドから属性を読む・書く

典型例として残高を持つ口座クラスを考えます。

メソッド内でself.balanceを更新し、その結果を返すようにすると使いやすくなります。

Python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        # 属性の読み取りと更新
        self.balance += amount
        return self.balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount
        return self.balance

    def owner_upper(self):
        # 状態を変えないメソッド
        return self.owner.upper()

acc = BankAccount("Bob", 100)
print(acc.deposit(50))    # 150
print(acc.withdraw(70))   # 80
print(acc.balance)        # 80
print(acc.owner_upper())  # "BOB"
実行結果
150
80
80
BOB

外部からメソッドを呼ぶ(obj.method())

通常はobj.method()の形で呼びます。

これはClass.method(obj, ...)という呼び出しの省略記法です。

次の例では、どちらも同じ結果を返します。

Python
acc = BankAccount("Bob", 100)
print(acc.owner_upper())                 # 一般的な呼び方
print(BankAccount.owner_upper(acc))      # クラスから明示的にselfを渡す
実行結果
BOB
BOB

よくあるつまずきとベストプラクティス

__init__前に属性へアクセスしない

属性は__init__で初期化してから使うのが原則です。

初期化前にアクセスするとAttributeErrorになります。

Python
# 悪い例: nameを初期化していない
class Bad:
    def greet(self):
        return f"Hi {self.name}"

b = Bad()
print(b.greet())  # ここでAttributeError
実行結果
Traceback (most recent call last):
  File "bad.py", line 8, in <module>
    print(b.greet())
  File "bad.py", line 4, in greet
    return f"Hi {self.name}"
AttributeError: 'Bad' object has no attribute 'name'
Python
# 良い例: __init__で必須属性を初期化
class Good:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hi {self.name}"

g = Good("Dana")
print(g.greet())
実行結果
Hi Dana

名前のtypoを防ぐ(エディタ補完の活用)

Pythonは柔軟で、意図せず新しい属性を作ってしまうことがあります。

typo(綴り誤り)は静かにバグを生みやすいため、エディタの補完・リンタを活用しましょう。

Python
class TypoProne:
    def __init__(self):
        self.count = 0

t = TypoProne()
t.cout = 1            # "count"のつもりがtypo。新しい属性coutが作られてしまう
print(t.count)        # 0のまま
print(hasattr(t, "cout"))  # True: 意図しない属性が存在している
実行結果
0
True

補完で属性名を選択するリンタや型チェッカーを有効化するといった対策が有効です。

役割の分離: データは属性、処理はメソッド

設計上の指針として、オブジェクトの「状態」は属性に、「その状態を扱う手続き」はメソッドに分離しましょう。

これにより、オブジェクトの責務が明確になり、読みやすくテストしやすいコードになります。

例えば、口座残高を直接書き換えるのではなくdeposit()withdraw()を通じて更新すると、整合性チェックを一元化できます。

型ヒントで属性を明示する

型ヒントを使うと、どんな値が入るかを機械と人に伝えられるため、補完やチェックが強化され、誤用の早期発見につながります。

Python
from typing import Optional

class Player:
    def __init__(self, name: str, level: int = 1) -> None:
        # __init__内でのインスタンス属性の型注釈
        self.name: str = name
        self.level: int = level
        self.nickname: Optional[str] = None  # 後から設定する可能性のある属性

    def set_nickname(self, nick: str) -> None:
        self.nickname = nick

p = Player("Eve")
print(p.level)      # 1
p.set_nickname("Evie")
print(p.nickname)   # "Evie"
実行結果
1
Evie

型ヒントは実行時の挙動を変えませんが、エディタ補完や型チェッカー(mypyなど)と組み合わせると開発体験が大きく向上します。

まとめ

本記事では、属性はオブジェクト固有の状態、メソッドはその状態に基づくふるまいという基本原則を中心に、__init__での属性初期化、ドット記法によるアクセス、メソッドの定義と呼び出し、そしてselfの本質について解説しました。

typoによる意図しない属性作成や初期化漏れは典型的な落とし穴です。

エディタ補完と型ヒントを活用し、データは属性、処理はメソッドという役割分離を徹底することで、堅牢で読みやすいクラス設計が実現できます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!