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引数が self
  • self.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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?