- Python
Pythonクラスの使い方|__init__とselfを実行結果つきで図解
Pythonのクラスとは、データ(変数)とそれを扱う処理(メソッド)をひとまとめにした「設計図」です。設計図から作った実物をインスタンスと呼び、__init__ はインスタンスを作った直後に呼ばれる初期化メソッド、self は「いま処理しているインスタンス自身」を指す引数です。
self がわかりにくい理由は、呼び出すときには書かないのに、定義では必ず書くからです。この記事では、t.rename("x") が Task.rename(t, "x") とまったく同じ意味であることを実際に動かして確かめ、self の正体を図と出力で見せます。そのうえで、クラス変数のリストが全インスタンスで共有される現象の再現、@classmethod・@staticmethod・@property の使い分け表、継承と super()、__str__・__repr__・__eq__、よくあるエラー5種の実際のメッセージまで、すべてのコードを Python 3.14.7 で実行した結果つきで解説します。
Pythonクラスの書き方早見表
| やりたいこと | 書き方 | ポイント |
|---|---|---|
| クラスを定義する | class Task: |
クラス名は先頭大文字(CapWords) |
| 初期値を受け取る | def __init__(self, title): |
インスタンス生成直後に自動で呼ばれる |
| インスタンス変数を作る | self.title = title |
self. を付けるとインスタンスに保存 |
| インスタンスを作る | t = Task("資料作成") |
self には渡さない(自動で入る) |
| メソッドを定義・呼び出す | def complete(self): → t.complete() |
t.complete() は Task.complete(t) と同じ |
| 全インスタンス共通の値 | クラス直下に rate = 0.1 |
変更は クラス名.rate = ... で行う |
| リスト・辞書を持たせる | __init__ 内で self.items = [] |
クラス直下に書くと共有される |
| 別の形式から作る | @classmethod def from_csv(cls, line): |
第1引数は cls(クラス) |
| self を使わない補助関数 | @staticmethod def is_valid(code): |
第1引数なし |
| 計算で出す属性・入力チェック | @property / @x.setter |
obj.x と括弧なしで読める |
| 親クラスを引き継ぐ | class Premium(Member): |
super().__init__(...) で親の初期化 |
| print の表示を変える | __str__ / __repr__ |
迷ったら __repr__ を先に |
== の比較を決める |
__eq__(必要なら __hash__ も) |
既定は「同じオブジェクトか」 |
| 定型の短縮 | @dataclass |
__init__・__repr__・__eq__ を自動生成 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pythonクラスの使い方|__init__とselfを実行結果つきで図解」
クラスの基本:定義・インスタンス生成・init
いちばん小さい完成形
class Task:
def __init__(self, title, done=False):
print(f"__init__ が呼ばれた: title={title}")
self.title = title
self.done = done
def complete(self):
self.done = True
return f"{self.title} を完了にしました"
t1 = Task("資料作成")
t2 = Task("在庫確認")
print(t1.title, t1.done)
print(t2.title, t2.done)
print(t1.complete())
print(t1.done, t2.done)
print(type(t1))
print(t1.__dict__)
print(t2.__dict__)
実行結果(Python 3.14.7):
__init__ が呼ばれた: title=資料作成
__init__ が呼ばれた: title=在庫確認
資料作成 False
在庫確認 False
資料作成 を完了にしました
True False
<class '__main__.Task'>
{'title': '資料作成', 'done': True}
{'title': '在庫確認', 'done': False}
読み取れることは3つです。
Task("資料作成")と書いただけで__init__が自動で呼ばれています。t1.complete()で変わったのはt1のdoneだけで、t2はFalseのままです。__dict__を見ると、インスタンスごとに別々の入れ物(辞書)があり、self.title = ...はそこに保存していることがわかります。
| 用語 | 意味 | この例では |
|---|---|---|
| クラス | 設計図 | Task |
| インスタンス | 設計図から作った実物 | t1、t2 |
| インスタンス変数(属性) | インスタンスごとに持つ値 | title、done |
| メソッド | クラスの中に書いた関数 | __init__、complete |
selfの正体:Class.method(obj) に書き換えるとわかる
呼び出しの書き換え
Pythonでは obj.method(引数) という呼び出しは、クラス.method(obj, 引数) と同じ意味です。Python公式チュートリアルにも「x.f() という呼び出しは、MyClass.f(x) と厳密に等価」と書かれています。実際に両方の書き方で動かしてみます。
class Task:
def __init__(self, title):
self.title = title
def rename(self, new_title):
print("self は t と同じもの?", self is t)
self.title = new_title
t = Task("資料作成")
# 1. ふつうの書き方
t.rename("資料作成(第2版)")
print(t.title)
# 2. まったく同じ意味の書き方:クラスの関数に t を第1引数として渡す
Task.rename(t, "資料作成(第3版)")
print(t.title)
# 3. __init__ も同じ。Task("x") はおおまかに次の2ステップ
u = Task.__new__(Task) # 空のインスタンスを作る
Task.__init__(u, "備品発注") # self=u として初期化する
print(u.title)
# メソッドの正体
print(Task.rename)
print(t.rename)
print(t.rename.__self__ is t, t.rename.__func__ is Task.rename)
実行結果(Python 3.14.7、アドレスは実行ごとに変わります):
self は t と同じもの? True
資料作成(第2版)
self は t と同じもの? True
資料作成(第3版)
備品発注
<function Task.rename at 0x7fb6e73a3110>
<bound method Task.rename of <__main__.Task object at 0x7fb6e7394590>>
True True
どちらの書き方でも self is t は True です。つまり self は特別なキーワードではなく、「メソッドの第1引数に、呼び出し元のインスタンスが自動で入る」というルールの、その第1引数につけた名前です。
flowchart LR
A["t.rename('第2版')"] -->|Pythonが書き換え| B["Task.rename(t, '第2版')"]
B --> C["def rename(self, new_title)<br>self = t / new_title = '第2版'"]
bound method(束縛メソッド)
上の出力で、Task.rename はただの関数(function)、t.rename は bound method と表示されています。bound method は「関数」と「t」をセットにしたもので、__self__ に t、__func__ に元の関数を持っています。t.rename(...) と呼ぶと、セットになっている t が第1引数として先頭に差し込まれる、これが self の仕組みです。
| 書き方 | 中身 | self に入るもの |
|---|---|---|
Task.rename |
ただの関数 | 自分で渡す必要がある |
t.rename |
bound method(関数 + t) | 自動で t |
Task("x") |
__new__ で空のインスタンスを作り、__init__(そのインスタンス, "x") |
作りたてのインスタンス |
self という名前は決まり?
名前は慣習で、文法上は何でも動きます。ただし PEP 8(Pythonのスタイルガイド)で「インスタンスメソッドの第1引数は常に self」とされていて、エディタや他の人も self を前提に読むため、必ず self と書くのがおすすめです。
インスタンス変数とクラス変数の違い
基本の動き
class Member:
company = "Sample株式会社" # クラス変数:全インスタンスで共有
count = 0
def __init__(self, name):
self.name = name # インスタンス変数:インスタンスごと
Member.count += 1
a = Member("佐藤")
b = Member("鈴木")
print(a.company, b.company, Member.company)
print("人数:", Member.count)
Member.company = "Sample合同会社" # クラス経由で変更 → 全員に反映
print(a.company, b.company)
a.company = "別会社" # インスタンス経由で代入 → a にだけ新しい変数ができる
print(a.company, b.company, Member.company)
print(a.__dict__)
print(b.__dict__)
Sample株式会社 Sample株式会社 Sample株式会社
人数: 2
Sample合同会社 Sample合同会社
別会社 Sample合同会社 Sample合同会社
{'name': '佐藤', 'company': '別会社'}
{'name': '鈴木'}
属性を読むときは「インスタンス → クラス」の順に探します。b.__dict__ に company は無いので、クラスの company が見えています。一方、a.company = "別会社" という代入はクラス変数を書き換えず、a の中に同名のインスタンス変数を新しく作ります。クラス変数を変えたいときは Member.company = ... とクラス名から代入します。
| 比較項目 | インスタンス変数 | クラス変数 |
|---|---|---|
| 書く場所 | __init__ などの中で self.x = ... |
クラス直下に x = ... |
| 持ち主 | 各インスタンス | クラス(全員で1つ) |
| 向いている値 | 名前、数量、状態など個別の値 | 定数、設定値、件数カウンタ |
| 変更方法 | obj.x = ... |
ClassName.x = ... |
リストをクラス変数にすると共有される(再現)
最もよく起きるのが、リストや辞書をクラス直下に書いてしまうケースです。
class Cart:
items = [] # リストをクラス変数にしてしまった
def __init__(self, owner):
self.owner = owner
def add(self, item):
self.items.append(item)
c1 = Cart("会員A")
c2 = Cart("会員B")
c1.add("ノート")
c2.add("ペン")
print(c1.owner, c1.items)
print(c2.owner, c2.items)
print(c1.items is c2.items, c1.items is Cart.items)
会員A ['ノート', 'ペン']
会員B ['ノート', 'ペン']
True True
会員Aのカートに、会員Bが入れたペンまで入りました。self.items.append(...) は「代入」ではなく「既存のリストの変更」なので、インスタンスに新しい変数は作られず、クラスにある1つのリストを全員で変更しています。is が True なのがその証拠です。
直し方は、__init__ の中で毎回新しいリストを作ることです。
class FixedCart:
def __init__(self, owner):
self.owner = owner
self.items = [] # __init__ の中で毎回新しいリストを作る
def add(self, item):
self.items.append(item)
f1 = FixedCart("会員A")
f2 = FixedCart("会員B")
f1.add("ノート")
f2.add("ペン")
print(f1.owner, f1.items)
print(f2.owner, f2.items)
会員A ['ノート']
会員B ['ペン']
引数の既定値にリストを書いた場合も同じことが起きます。
class DefaultArgCart:
def __init__(self, owner, items=[]): # 既定値のリストも同じ現象
self.owner = owner
self.items = items
d1 = DefaultArgCart("会員A"); d1.items.append("ノート")
d2 = DefaultArgCart("会員B")
print(d2.items)
['ノート']
既定値は関数を定義したときに1回だけ作られるためです。items=None にして self.items = [] if items is None else list(items) と書くと、インスタンスごとに別のリストになります。
classmethod・staticmethod・propertyの使い分け
使い分け表
| 種類 | 第1引数 | 呼び出し方 | 何に使うか |
|---|---|---|---|
| ふつうのメソッド | self(インスタンス) |
obj.m() |
インスタンスの値を読む・変える |
@classmethod |
cls(クラス) |
Cls.m() / obj.m() |
別の形式からインスタンスを作る(from_csv など)、クラス変数の操作 |
@staticmethod |
なし | Cls.m() / obj.m() |
self も cls も使わない補助関数をクラスにまとめる |
@property |
self |
obj.m(括弧なし) |
計算で出す属性、読み取り専用にする |
@x.setter |
self, value |
obj.x = 値 |
代入時の入力チェック |
迷ったときの判断基準はシンプルです。インスタンスの値を使うならふつうのメソッド、「インスタンスを作る別の入口」なら classmethod、どちらも使わないなら staticmethod(またはモジュールの関数)です。
4つをまとめて動かす
class Item:
tax_rate = 0.10
def __init__(self, name, price):
self.name = name
self.price = price # setter を通る
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError(f"price は0以上にしてください: {value}")
self._price = value
@property
def price_with_tax(self): # 計算で出す読み取り専用の属性
return round(self.price * (1 + self.tax_rate))
@classmethod
def from_csv(cls, line): # 別の形式からインスタンスを作る
name, price = line.split(",")
return cls(name, int(price))
@staticmethod
def is_valid_code(code): # self も cls も使わない補助関数
return code.isdigit() and len(code) == 6
i = Item("ノート", 300)
print(i.price, i.price_with_tax)
j = Item.from_csv("ペン,150")
print(j.name, j.price, type(j).__name__)
print(Item.is_valid_code("123456"), Item.is_valid_code("12AB"))
i.price = 500
print(i.price_with_tax)
try:
i.price = -1
except ValueError as e:
print("ValueError:", e)
try:
i.price_with_tax = 1000
except AttributeError as e:
print("AttributeError:", e)
class Food(Item):
tax_rate = 0.08
k = Food.from_csv("お弁当,500") # cls が Food になる
print(type(k).__name__, k.price_with_tax)
300 330
ペン 150 Item
True False
550
ValueError: price は0以上にしてください: -1
AttributeError: property 'price_with_tax' of 'Item' object has no setter
Food 540
ポイントは最後の2行です。Food.from_csv(...) では cls に Food が入るため、子クラスから呼ぶと子クラスのインスタンスが作られ、税率8%で計算されます。from_csv の中で Item(...) と書かずに cls(...) と書くのはこのためです。setter のない property に代入すると、has no setter の AttributeError になり、読み取り専用として働きます。
継承とsuper()
親の処理を引き継いで足す
class Member:
def __init__(self, name, email):
self.name = name
self.email = email
def greet(self):
return f"{self.name}さん、こんにちは"
class PremiumMember(Member):
def __init__(self, name, email, rank):
super().__init__(name, email) # 親の __init__ に任せる
self.rank = rank
def greet(self): # オーバーライド
base = super().greet() # 親のメソッドを呼んでから足す
return f"{base}({self.rank}会員)"
p = PremiumMember("佐藤", "sato@example.com", "ゴールド")
print(p.greet())
print(p.email)
print(isinstance(p, Member), issubclass(PremiumMember, Member))
print([c.__name__ for c in PremiumMember.__mro__])
佐藤さん、こんにちは(ゴールド会員)
sato@example.com
True True
['PremiumMember', 'Member', 'object']
super() は「親クラス(正確には MRO で次のクラス)のメソッドを呼ぶ」ための関数です。__mro__(メソッド解決順序)は、メソッドを探す順番を表します。子で同名のメソッドを定義すると親のものを上書き(オーバーライド)し、super().greet() で親の処理も使えます。
super().init を呼び忘れると
class BrokenMember(Member):
def __init__(self, name, email, rank):
self.rank = rank # super().__init__ を呼び忘れた
b = BrokenMember("鈴木", "suzuki@example.com", "シルバー")
try:
print(b.greet())
except AttributeError as e:
print("AttributeError:", e)
AttributeError: 'BrokenMember' object has no attribute 'name'
子で __init__ を書くと、親の __init__ は自動では呼ばれません。親が作るはずの self.name が無いため、greet() の中でエラーになります。子の __init__ の最初で super().__init__(...) を呼ぶと解決します。
抽象メソッドで「子クラスに必ず実装させる」書き方は「Python 抽象クラスとProtocol」の記事で詳しく解説しています。
str・repr・__eq__で表示と比較を整える
何も定義しないと
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p)
print(p == Point(1, 2))
<__main__.Point object at 0x7ff313594590>
False
既定の表示はクラス名とアドレスだけ、== は「同じオブジェクトかどうか」で判定されます。中身が同じでも別々に作ったものは False です。
定義した場合
class Point2:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point2(x={self.x!r}, y={self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
def __eq__(self, other):
if not isinstance(other, Point2):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
q = Point2(1, 2)
print(q)
print(repr(q))
print([q, Point2(3, 4)])
print(f"{q} / {q!r}")
print(q == Point2(1, 2), q == (1, 2))
print(len({q, Point2(1, 2)}))
(1, 2)
Point2(x=1, y=2)
[Point2(x=1, y=2), Point2(x=3, y=4)]
(1, 2) / Point2(x=1, y=2)
True False
1
| メソッド | 使われる場面 | 書く内容の目安 |
|---|---|---|
__str__ |
print(obj)、str(obj)、f-string の {obj} |
人が読むための表示 |
__repr__ |
repr(obj)、リストの中の表示、対話モード、{obj!r} |
開発者向け。作り直せる形が理想 |
__eq__ |
== |
比較したい値をタプルにして比べる |
__hash__ |
set の要素、dict のキー |
__eq__ と同じ値から計算 |
__str__ が無いときは __repr__ が使われるので、1つだけ書くなら __repr__ を先に書きます。また、__eq__ だけ定義すると __hash__ が None になり、set や dict のキーに使えなくなります(実際の出力)。
TypeError: cannot use 'OnlyEq' as a set element (unhashable type: 'OnlyEq')
よくあるエラーと直し方(実行結果)
すべて Python 3.14.7 で実際に出たメッセージです。
class Task:
def __init__(self, title):
self.title = title
def show(): # self を書き忘れた
print("表示")
t = Task("資料作成")
try:
t.show()
except TypeError as e:
print("1) TypeError:", e)
try:
Task()
except TypeError as e:
print("2) TypeError:", e)
class Task2:
def __init__(self, title):
title = title # self. を付け忘れた
t2 = Task2("資料作成")
try:
print(t2.title)
except AttributeError as e:
print("3) AttributeError:", e)
class Task3:
def __init__(self, title):
self.title = title
def show(self):
return title # self.title と書くべきところ
try:
Task3("x").show()
except NameError as e:
print("4) NameError:", e)
class Task4:
def __int__(self, title): # __init__ のつづり間違い
self.title = title
try:
Task4("資料作成")
except TypeError as e:
print("5) TypeError:", e)
1) TypeError: Task.show() takes 0 positional arguments but 1 was given
2) TypeError: Task.__init__() missing 1 required positional argument: 'title'
3) AttributeError: 'Task2' object has no attribute 'title'
4) NameError: name 'title' is not defined
5) TypeError: Task4() takes no arguments
| No. | 原因 | 直し方 |
|---|---|---|
| 1 | メソッド定義で self を書き忘れた。t.show() は Task.show(t) なので、引数0個の関数に1個渡している |
def show(self): にする |
| 2 | __init__ が受け取る引数を渡していない |
Task("資料作成") と渡す、または既定値を付ける |
| 3 | self. を付けずに代入したので、ただのローカル変数で終わった |
self.title = title |
| 4 | メソッド内で self. を付けずに参照した |
return self.title |
| 5 | __init__ のつづり間違い(__int__、_init_ など)で、引数を受け取る初期化メソッドが無い |
アンダースコア2つずつの __init__ |
1番の「takes 0 positional arguments but 1 was given」は、self の仕組みがわかると読み解けます。「0個の引数を受け取る関数に、(自動で差し込まれた self の分として)1個渡された」という意味です。
dataclassで書き換えるとこうなる
__init__ で値を代入するだけ、__repr__・__eq__ も定型、というクラスは @dataclass で短く書けます。
from dataclasses import dataclass, field
@dataclass
class Task:
title: str
done: bool = False
tags: list[str] = field(default_factory=list)
def complete(self):
self.done = True
t = Task("資料作成")
t.tags.append("急ぎ")
print(t)
print(t == Task("資料作成", tags=["急ぎ"]))
t.complete()
print(t)
Task(title='資料作成', done=False, tags=['急ぎ'])
True
Task(title='資料作成', done=True, tags=['急ぎ'])
__init__・__repr__・__eq__ が自動で作られ、リストの既定値も default_factory でインスタンスごとに別になります。
| 向いている書き方 | こんなとき |
|---|---|
| ふつうのクラス | 初期化で複雑な処理をする、振る舞い(メソッド)が中心 |
@dataclass |
データを入れておくのが主な役割、比較や表示を手早く整えたい |
frozen・slots・継承時の注意などは「Python dataclassの使い方」の記事でまとめています。
よくある質問
Q. __init__ はコンストラクタですか?
A. 一般にはコンストラクタと呼ばれますが、厳密にはインスタンスを作るのは __new__、作られたものを初期化するのが __init__ です。ふだんは __init__ だけ書けば十分です。
Q. self を省略して書ける方法はありますか?
A. インスタンスメソッドでは省略できません。self を使わない処理なら @staticmethod にするか、クラスの外の関数にします。
Q. private 変数はどう書きますか?
A. Pythonにアクセス制限はありませんが、_name(アンダースコア1つ)で「内部用」という慣習を示します。__name(2つ)は名前が _クラス名__name に変換され、子クラスとの名前の衝突を避けたいときに使います。
Q. クラスはどんなときに使えばよいですか? A. 同じデータに対する処理が複数ある、同じ形のデータをたくさん作るときです。関数1つで済む処理なら関数のままで問題ありません。
まとめ
- クラスは設計図、インスタンスは実物。
__init__は生成直後に自動で呼ばれる初期化メソッド obj.method(x)はClass.method(obj, x)と同じ。この第1引数がselfself.x = ...でインスタンスに保存。self.を忘れるとローカル変数で終わる- リスト・辞書をクラス直下に書くと全インスタンスで共有。
__init__で毎回作る - 別の入口は
@classmethod(cls)、補助関数は@staticmethod、計算属性・入力チェックは@property - 子の
__init__ではsuper().__init__()を最初に呼ぶ - 表示は
__repr__から、__eq__を書いたら__hash__も。定型なら@dataclass
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Pythonクラスの使い方|__init__とselfを実行結果つきで図解」
株式会社RJCでは、Pythonのクラス設計の基本を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。