落とし穴: クラスを作りなおしても前のクラスのデータを持っている
このコードを走らせたときにハマった。
from dataclasses import dataclass, field
from typing import List
@dataclass
class Struct:
x: str
@dataclass
class Container:
content: List[Struct] = field(default_factory=list)
@dataclass
class ContainerWrapper:
container: Container = Container()
def append(self, s: Struct) -> None:
self.container.content.append(s)
return None
def store_structs(self, s_list: List[Struct]) -> None:
for s in s_list:
self.append(s)
return None
s_list = [Struct("foo")]
ContainerWrapper().store_structs(s_list)
# recreated instance strangely contains old instance's data.
wrapper = ContainerWrapper()
print(wrapper.container) #=> Container(content=[Struct(x='foo')])
なんでContentainerWrapper
クラスを作り直したあとも作り直す前のインスタンスのデータを持っとるんやってなっでハマった。
種明かし: 実は@dataclass
に問題があるみたい
Pythonのドキュメントでは、@dataclass
デコレータを用いてデフォルトの値を設定すると、 __init__()
でデフォルト値を設定したのと同じように、インスタンス変数に値が代入されると主張している。
すなわち、以下の2つのコードは等価であるはずだ。
from dataclasses import dataclass
@dataclass
class Foo:
x: int = 0
class Foo:
def __init__(self, x: int = 0) -> None:
self.x = x
しかし、python 3.8.6と3.9.0で試してみた限り、@dataclass
を使って定義したデフォルト値はクラス変数として扱われてしまった。
from dataclasses import dataclass
@dataclass
class Foo:
x: int = 0
@classmethod
def print_x(cls):
print(cls.x)
Foo.print_x() #=> 0
クラス変数とインスタンス変数を取り違えれば問題が起こることはこの記事が詳しい。