クラス属性がインスタンス間で共有される
クラス直下に書いたリストや辞書は、全インスタンスで共有される1つのオブジェクトになります。インスタンスごとに独立させたい場合は__init__で作ります。
なぜエラーが出ないのか
出力: ['apple']
(エラーなし)- Pythonは何も報告しません。文法として正しいためです
出力: ['apple']- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 class Cart: 2 items = [] 3 def __init__(self): 4 pass ^ 5 def add(self, item): 6 self.items.append(item) 7 8 c1 = Cart() 9 c2 = Cart() 10 c1.add("apple") 11 print(c2.items)
出力: ['apple']期待: [] (エラーにはなりません)
クラス直下に書いたitems=[]は全インスタンスで共有される1つのリストです。__init__でself.itemsに新しいリストを代入すると、インスタンスごとに独立したリストになります。
直し方: pass を self.items = [] にします。
広告
広告スロット(未設定)
パターン2
1 class Product: 2 tags = [] 3 def __init__(self): 4 pass ^ 5 def tag(self, name): 6 self.tags.append(name) 7 8 p1 = Product() 9 p2 = Product() 10 p1.tag("sale") 11 print(p2.tags)
出力: ['sale']期待: [] (エラーにはなりません)
商品にタグを付けるような処理でも同じ罠にかかります。1つの商品に付けたタグが、無関係な別の商品にも表示されてしまいます。
直し方: pass を self.tags = [] にします。
パターン3
1 class Robot: 2 log = [] 3 def __init__(self): 4 pass ^ 5 def move(self, step): 6 self.log.append(step) 7 8 r1 = Robot() 9 r2 = Robot() 10 r1.move("north") 11 print(r2.log)
出力: ['north']期待: [] (エラーにはなりません)
動作ログを記録する場合も、__init__で自分専用のリストを作らないと他のインスタンスの記録と混ざります。
直し方: pass を self.log = [] にします。
よくある誤解
self.itemsのように書けばインスタンスごとの変数になると思いがちですが、代入していなければ同名のクラス属性を参照しているだけです。1つのインスタンスが変更すると全インスタンスに影響します。
まとめ
クラス属性がインスタンス間で共有されるは上級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。