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

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

Pythonの__init__と__del__をUserクラスで解説。初期化漏れによるAttributeError、必須引数で渡し忘れをTypeErrorで止める書き方、tags=[]の落とし穴、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=[])のような書き方が問題視される理由は何ですか?