Q1__init__(self, name) を持つクラス C に対し、C() を引数なしで呼ぶとどうなりますか?
コンストラクタ __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 が存在しない
__init__ で必須属性を強制する
__init__ はインスタンス生成時に Python が自動的に呼び出す特別なメソッドです。コンストラクタ とも呼ばれます。アンダースコア 2 つで囲まれた名前は ダンダーメソッド と呼ばれ、Python が特別なタイミングで呼ぶ約束ごとです。
__init__(self, name, email) のように引数を必須にしておけば、渡し忘れた瞬間に TypeError で止まります。User() のような不完全なインスタンスは作られないため、後ろのコードは「name と email は必ず入っている」という前提で書けるようになります。
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__ も普通の関数なので、デフォルト引数 を持たせられます。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 = [] を書くのが定石です。
__del__ — インスタンス削除時の後始末
__init__ と対になるダンダーメソッドが __del__(デストラクタ) です。__init__ がインスタンス生成時に呼ばれるのに対し、__del__ は インスタンスが破棄される瞬間 に Python が自動的に呼びます。
破棄のきっかけは大きく 2 つあります。
- 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() でメモリ参照を見ながら整理します。
理解度チェック
まずは1問ずつ答えてみましょう。
Q2__del__ が呼ばれるタイミングとして正しいのはどれですか?
Q3def __init__(self, tags=[]) のような書き方が問題視される理由は何ですか?