Pythonのクラスでは、データを入れる箱としての属性(インスタンス変数)と、そのデータを使って処理を行うメソッドを組み合わせて設計します。
属性はオブジェクトが持つ状態、メソッドはその状態に基づくふるまいです。
本記事では、初心者がつまずきやすいポイントを避けつつ、self
の意味、属性の定義とアクセス、メソッドの書き方までを段階的に解説します。
クラスの属性(インスタンス変数)とメソッドの基本
属性(インスタンス変数)とは
属性(インスタンス変数)は、生成したオブジェクトごとに値が異なるデータを指します。
例えばユーザーなら名前や年齢、口座なら残高などが該当します。
同じクラスから作ったオブジェクトでも、属性の中身はオブジェクトごとに独立しています。
属性はクラス定義内で初期化するのが基本で、各オブジェクトの「状態」を表現します。
次の観点で理解しておくと整理しやすいです。
- 属性は「値」そのものを保持します。
- プログラムからは
obj.attr
というドット記法で参照します。 - 値は実行中に更新できます。
以下の表は、属性とメソッドの違いを簡潔にまとめたものです。
観点 | 属性(インスタンス変数) | メソッド |
---|---|---|
役割 | 状態やデータを保持 | データに基づく処理を提供 |
例 | name 、age 、balance | greet() 、deposit() |
呼び出し方 | obj.attr | obj.method() |
変更 | 代入で変更 | 実行で状態を変える場合がある |
メソッドとは
メソッドはクラス内に定義する関数で、特定のオブジェクトの文脈で動作します。
インスタンスメソッドの第1引数は慣例的にself
と書き、そのオブジェクト自身を受け取ります。
これにより、そのオブジェクトの属性を読み書きできるようになります。
処理のまとまりをメソッドとして定義することで、コードの再利用性と可読性が向上します。
selfが指すものと属性・メソッドの関係
self
はメソッドが呼ばれた「そのオブジェクト自身」を示します。
実際にid(self)
とid(obj)
を比べると同一であることがわかります。
つまりメソッドの内部からself.attr
としてアクセスすると、そのオブジェクトが持つ属性に触れていることになります。
# 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 = 値
と代入すると、そのオブジェクトに属性が作られます。
# 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では柔軟に属性を更新できます。
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
経由で属性を更新できます。
状態の変化をメソッドに閉じ込めると、使う側は意図が明確になり、バグを減らせます。
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
が発生します。
綴りの誤りにも注意が必要です。
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
を介して属性へアクセスできます。
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!
引数と戻り値の扱い
メソッドは通常の関数と同様に、引数を受け取り、値を返せます。
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
を更新し、その結果を返すようにすると使いやすくなります。
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, ...)
という呼び出しの省略記法です。
次の例では、どちらも同じ結果を返します。
acc = BankAccount("Bob", 100)
print(acc.owner_upper()) # 一般的な呼び方
print(BankAccount.owner_upper(acc)) # クラスから明示的にselfを渡す
BOB
BOB
よくあるつまずきとベストプラクティス
__init__前に属性へアクセスしない
属性は__init__
で初期化してから使うのが原則です。
初期化前にアクセスするとAttributeError
になります。
# 悪い例: 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'
# 良い例: __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(綴り誤り)は静かにバグを生みやすいため、エディタの補完・リンタを活用しましょう。
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()
を通じて更新すると、整合性チェックを一元化できます。
型ヒントで属性を明示する
型ヒントを使うと、どんな値が入るかを機械と人に伝えられるため、補完やチェックが強化され、誤用の早期発見につながります。
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による意図しない属性作成や初期化漏れは典型的な落とし穴です。
エディタ補完と型ヒントを活用し、データは属性、処理はメソッドという役割分離を徹底することで、堅牢で読みやすいクラス設計が実現できます。