Q1__init__(self, name)を持つクラスCに対し、C()を引数なしで呼ぶとどうなりますか?
コンストラクタ __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 が存在しない
__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=[])のような書き方が問題視される理由は何ですか?