順番に読み進めながら学べます

コンストラクタ __init__ とデストラクタ __del__

Python の __init__ と __del__ を解説します。必須属性の初期化、デフォルト値、del 時に呼ばれる後始末メソッドの使いどころまで図解で押さえます。

前回はクラスとインスタンスの基本、self__init__ の最小形を見ました。今回はその続きとして、__init__「インスタンスを必ず正しい状態で生まれる」ようにするために使用します。引数を渡し忘れたら止める 必須化、省略可能にする デフォルト引数、そして対になる __del__(デストラクタ) までを整理します。

__init__ が無いと何が起きるか

クラス側で属性をきちんと初期化しないと、属性が抜け落ちたままインスタンスが作られてしまい、後でメソッドを呼んだ瞬間に AttributeError で落ちる、という事故が起きます。

下のコードでは、User クラスに set_name メソッドはあるものの初期化がないため、set_name を呼ぶ前に display() を実行すると self.name が無くて落ちます

class User:
    def set_name(self, name):
        self.name = name

    def display(self):
        print(self.name)

user = User()
user.display()             # AttributeError: name 属性が無い
# user.set_name("太郎") を先に呼ばないと self.name が存在しない

属性を初期化せずにメソッドを呼ぶと AttributeError が出ることを確認します。実行してエラーになることを確かめてください。

Python エディタ

コードを実行してください

__init__ で必須属性を強制する

__init__ はインスタンス生成時に Python が自動的に呼び出す特別なメソッドです。コンストラクタ とも呼ばれます。アンダースコア 2 つで囲まれた名前は ダンダーメソッド と呼ばれ、Python が特別なタイミングで呼ぶ約束ごとです。

__init__(self, name, email) のように引数を必須にしておけば、渡し忘れた瞬間に TypeError で止まります。User() のような不完全なインスタンスは作られないため、後ろのコードは「name と email は必ず入っている」という前提で書けるようになります。

__init__ がインスタンス生成の流れに組み込まれる
User('太郎', 'taro@x.jp')空インスタンスを生成__init__を呼ぶuser属性が揃った
User("太郎", "taro@x.jp") を呼ぶと、Python は空のインスタンスを作り、__init__ を呼び、属性を埋めて、完成したインスタンスを返す。引数を渡し忘れれば __init__ の段階で TypeError。
class User:
    def __init__(self, name, email):    # 必須引数
        print("__init__ が呼ばれた")
        self.name = name
        self.email = email

    def display(self):
        print(f"{self.name} <{self.email}>")

user = User("太郎", "taro@example.com")  # user.name に "太郎" / user.email に "taro@example.com" を設定
# __init__ が呼ばれた
user.display()
# 太郎 <taro@example.com>

# 引数を忘れると TypeError
# User()  # → TypeError: missing 2 required positional arguments

__init__ に必須引数を 2 つ書いて、生成と表示、そして渡し忘れたときの挙動まで体験します。

class User: を定義し、__init__(self, name, email) の中で self.name = nameself.email = email を書いてください。

def display(self): を定義し、print(f"{self.name} <{self.email}>") を表示してください。

taro = User("太郎", "taro@example.com") を作り、taro.display() を呼んで 太郎 <taro@example.com> と表示されることを確認してください。

④ さらに jiro = User("次郎") のように email を渡し忘れたインスタンス を作ろうとして、TypeError で止まることを確認してください(try / except で受け止めて中身を表示すると分かりやすいです)。

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

デフォルト引数で省略可能にする

__init__ も普通の関数なので、デフォルト引数 を持たせられます。age=0 のように書いておけば、呼び出し側が省略すれば既定値が、渡せばその値が使われます。「必須にしたい属性」と「任意で受け取る属性」を分けて設計できます。

class User:
    def __init__(self, name, email, age=0):   # age は省略可能
        self.name = name
        self.email = email
        self.age = age

taro = User("太郎", "taro@example.com")            # age 省略 → 0
hanako = User("花子", "hanako@example.com", 30)    # age を指定

print(taro.age)     # 0
print(hanako.age)   # 30

デフォルト引数に list / dict を直接書かない

def __init__(self, tags=[]) のように 可変オブジェクト をデフォルトに置くと、全インスタンスで同じリストを共有してしまう有名な落とし穴があります。詳しくは 関数の引数と変更可能性 で扱っています。__init__ でも同じ罠なので、tags=None にしておいて関数の中で if tags is None: tags = [] を書くのが定石です。

デフォルト引数に 空のリスト を直接書いてしまうと、どんな問題が起きるかを実際に手を動かして確認します。

class User: を定義し、__init__(self, name, tags=[]) の中で self.name = nameself.tags = tags を書いてください(あえて tags=[] を直書きします)。

taro = User("太郎")hanako = User("花子") を作ってください。

taro.tags.append("vip") を実行したあと、print(taro.tags)print(hanako.tags) を呼び、hanako 側にも "vip" が混ざる ことを確認してください。

④ 最後に print(taro.tags is hanako.tags) を実行し、同じリストを共有している ことを確かめてください。

Python エディタ

コードを実行してください

__del__ — インスタンス削除時の後始末

__init__ と対になるダンダーメソッドが __del__(デストラクタ) です。__init__ がインスタンス生成時に呼ばれるのに対し、__del__インスタンスが破棄される瞬間 に Python が自動的に呼びます。

破棄のきっかけは大きく 2 つあります。

- del 変数名 で明示的に削除したとき

- プログラム終了時にメモリが解放されるとき(残っていたインスタンスは順番に破棄される)

後者があるため、__del__こちらが何も書かなくてもプログラム終了時に呼ばれます。

__init__ と __del__ のタイミング
User('太郎')を呼ぶ__init__呼ばれるuser生存中del user/ プログラム終了__del__呼ばれるメモリ解放生成完了破棄後処理
上段は __init__ のフロー — 生まれるとき に Python が自動で呼ぶ。下段は __del__ のフロー — del でもプログラム終了でも、消えるとき に自動で呼ばれる。

ブラウザ実行では __del__ は動きません

本サイトの ブラウザ実行は MicroPython をベースにしており、仕様上 __del__ は呼び出されません。本セクションのコード例は 読んで動きを把握する 用です。実際の動作確認は PC のターミナルなど CPython 環境 で行ってください。

class User:
    def __init__(self, name):
        print(f"{name} を作成")
        self.name = name

    def __del__(self):
        print(f"{self.name} を破棄")

taro = User("太郎")        # 太郎 を作成
hanako = User("花子")      # 花子 を作成

del taro                   # 太郎 を破棄  ← ここで明示的に呼ばれる
print("プログラム終了直前")
# プログラム終了直前
# 花子 を破棄  ← プログラム終了時に自動で呼ばれる

後始末の典型パターン — 一覧から外す

__del__ の典型的な使いどころは、「クラスがまとめて持っている一覧から、自分自身を取り除く」 といった後始末です。下の例では、User クラスのクラス変数 users生成時に名前を追加 し、破棄時に取り除く という対になる処理を入れています。

class User:
    users = []                         # クラス変数:現在生きているユーザー

    def __init__(self, name):
        self.name = name
        User.users.append(name)        # 生成時に登録

    def __del__(self):
        User.users.remove(self.name)   # 破棄時に取り除く

taro = User("太郎")
hanako = User("花子")
print(User.users)    # ['太郎', '花子']

del taro
print(User.users)    # ['花子']

実務では __del__ を直接書く場面は限定的

__del__ は呼ばれるタイミングが Python のガベージコレクタ任せで、いつ呼ばれるかが厳密には保証されません。ファイルやネットワーク接続の確実な解放には、__del__ ではなく with 文(コンテキストマネージャ) を使います。

今回は、__init__必須属性の強制デフォルト引数で実用的に使う書き方、そして対になる __del__ の正体と典型用法を見ました。これで「インスタンスが生まれてから消えるまで」の流れを Python の側から制御できるようになりました。

次回は、ここまで何気なく書いてきた self.x = ...正体 に踏み込みます。クラスに登場する 2 種類の変数 — クラス変数とインスタンス変数 — の違い、self.x = ... を書いた瞬間に何が新しく作られているかを、id() でメモリ参照を見ながら整理します。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1__init__(self, name) を持つクラス C に対し、C() を引数なしで呼ぶとどうなりますか?

Q2__del__ が呼ばれるタイミングとして正しいのはどれですか?

Q3def __init__(self, tags=[]) のような書き方が問題視される理由は何ですか?