[{"data":1,"prerenderedAt":817},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-dataclasses-slots-explained":3},{"id":4,"title":5,"body":6,"description":803,"difficulty":804,"extension":805,"framework":806,"frameworkSlug":54,"meta":807,"navigation":82,"order":105,"path":808,"qaPath":809,"seo":810,"stem":811,"subtopic":812,"topic":813,"topicSlug":814,"updated":815,"__hash__":816},"blog\u002Fblog\u002Fpython-dataclasses-slots-explained.md","Python Dataclasses and __slots__ Explained — @dataclass, frozen, and field()",{"type":7,"value":8,"toc":791},"minimark",[9,14,39,43,49,181,199,203,217,308,312,323,412,421,428,436,566,576,583,593,687,697,701,704,731,746,750,787],[10,11,13],"h2",{"id":12},"python-dataclasses-explained","Python dataclasses, explained",[15,16,17,18,22,23,26,27,30,31,34,35,38],"p",{},"Writing ",[19,20,21],"code",{},"__init__",", ",[19,24,25],{},"__repr__",", and ",[19,28,29],{},"__eq__"," by hand for a simple data-holding class is\ntedious boilerplate. ",[19,32,33],{},"@dataclass"," generates them from your type annotations. Pair it with\n",[19,36,37],{},"__slots__"," for memory savings and you have Python's modern answer to \"I just need a record\".",[10,40,42],{"id":41},"what-dataclass-generates","What @dataclass generates",[15,44,45,46,48],{},"Decorate a class with ",[19,47,33],{}," and annotate its fields — Python writes the dunder methods\nfor you:",[50,51,56],"pre",{"className":52,"code":53,"language":54,"meta":55,"style":55},"language-python shiki shiki-themes github-light github-dark","from dataclasses import dataclass\n\n@dataclass\nclass Point:\n    x: int\n    y: int\n\np = Point(1, 2)\np                # Point(x=1, y=2)        — generated __repr__\np == Point(1, 2) # True                   — generated __eq__\n","python","",[19,57,58,77,84,91,103,113,121,126,149,159],{"__ignoreMap":55},[59,60,63,67,71,74],"span",{"class":61,"line":62},"line",1,[59,64,66],{"class":65},"szBVR","from",[59,68,70],{"class":69},"sVt8B"," dataclasses ",[59,72,73],{"class":65},"import",[59,75,76],{"class":69}," dataclass\n",[59,78,80],{"class":61,"line":79},2,[59,81,83],{"emptyLinePlaceholder":82},true,"\n",[59,85,87],{"class":61,"line":86},3,[59,88,90],{"class":89},"sScJk","@dataclass\n",[59,92,94,97,100],{"class":61,"line":93},4,[59,95,96],{"class":65},"class",[59,98,99],{"class":89}," Point",[59,101,102],{"class":69},":\n",[59,104,106,109],{"class":61,"line":105},5,[59,107,108],{"class":69},"    x: ",[59,110,112],{"class":111},"sj4cs","int\n",[59,114,116,119],{"class":61,"line":115},6,[59,117,118],{"class":69},"    y: ",[59,120,112],{"class":111},[59,122,124],{"class":61,"line":123},7,[59,125,83],{"emptyLinePlaceholder":82},[59,127,129,132,135,138,141,143,146],{"class":61,"line":128},8,[59,130,131],{"class":69},"p ",[59,133,134],{"class":65},"=",[59,136,137],{"class":69}," Point(",[59,139,140],{"class":111},"1",[59,142,22],{"class":69},[59,144,145],{"class":111},"2",[59,147,148],{"class":69},")\n",[59,150,152,155],{"class":61,"line":151},9,[59,153,154],{"class":69},"p                ",[59,156,158],{"class":157},"sJ8bj","# Point(x=1, y=2)        — generated __repr__\n",[59,160,162,164,167,169,171,173,175,178],{"class":61,"line":161},10,[59,163,131],{"class":69},[59,165,166],{"class":65},"==",[59,168,137],{"class":69},[59,170,140],{"class":111},[59,172,22],{"class":69},[59,174,145],{"class":111},[59,176,177],{"class":69},") ",[59,179,180],{"class":157},"# True                   — generated __eq__\n",[15,182,183,184,22,186,26,188,190,191,194,195,198],{},"By default it generates ",[19,185,21],{},[19,187,25],{},[19,189,29],{},". Options like\n",[19,192,193],{},"@dataclass(order=True)"," add comparison methods, and ",[19,196,197],{},"frozen=True"," makes it immutable.",[10,200,202],{"id":201},"frozen-dataclasses","frozen dataclasses",[15,204,205,207,208,212,213,216],{},[19,206,197],{}," makes instances ",[209,210,211],"strong",{},"immutable"," — assigning to a field raises an error — and, as\na bonus, makes them ",[209,214,215],{},"hashable"," so they can be dict keys or set members:",[50,218,220],{"className":52,"code":219,"language":54,"meta":55,"style":55},"@dataclass(frozen=True)\nclass Point:\n    x: int\n    y: int\n\np = Point(1, 2)\np.x = 5          # FrozenInstanceError\n{p: \"origin-ish\"}   # hashable — works as a dict key\n",[19,221,222,240,248,254,260,264,280,293],{"__ignoreMap":55},[59,223,224,226,229,233,235,238],{"class":61,"line":62},[59,225,33],{"class":89},[59,227,228],{"class":69},"(",[59,230,232],{"class":231},"s4XuR","frozen",[59,234,134],{"class":65},[59,236,237],{"class":111},"True",[59,239,148],{"class":69},[59,241,242,244,246],{"class":61,"line":79},[59,243,96],{"class":65},[59,245,99],{"class":89},[59,247,102],{"class":69},[59,249,250,252],{"class":61,"line":86},[59,251,108],{"class":69},[59,253,112],{"class":111},[59,255,256,258],{"class":61,"line":93},[59,257,118],{"class":69},[59,259,112],{"class":111},[59,261,262],{"class":61,"line":105},[59,263,83],{"emptyLinePlaceholder":82},[59,265,266,268,270,272,274,276,278],{"class":61,"line":115},[59,267,131],{"class":69},[59,269,134],{"class":65},[59,271,137],{"class":69},[59,273,140],{"class":111},[59,275,22],{"class":69},[59,277,145],{"class":111},[59,279,148],{"class":69},[59,281,282,285,287,290],{"class":61,"line":123},[59,283,284],{"class":69},"p.x ",[59,286,134],{"class":65},[59,288,289],{"class":111}," 5",[59,291,292],{"class":157},"          # FrozenInstanceError\n",[59,294,295,298,302,305],{"class":61,"line":128},[59,296,297],{"class":69},"{p: ",[59,299,301],{"class":300},"sZZnC","\"origin-ish\"",[59,303,304],{"class":69},"}   ",[59,306,307],{"class":157},"# hashable — works as a dict key\n",[10,309,311],{"id":310},"the-mutable-default-trap-fielddefault_factory","The mutable default trap — field(default_factory)",[15,313,314,315,318,319,322],{},"Just like mutable default arguments, a mutable default on a dataclass field would be shared\nacross instances. Dataclasses ",[209,316,317],{},"forbid"," it outright and make you use ",[19,320,321],{},"field(default_factory=...)",":",[50,324,326],{"className":52,"code":325,"language":54,"meta":55,"style":55},"from dataclasses import dataclass, field\n\n@dataclass\nclass Cart:\n    items: list = []                          # ValueError at class definition!\n\n@dataclass\nclass Cart:\n    items: list = field(default_factory=list) # correct — fresh list per instance\n",[19,327,328,339,343,347,356,373,377,381,389],{"__ignoreMap":55},[59,329,330,332,334,336],{"class":61,"line":62},[59,331,66],{"class":65},[59,333,70],{"class":69},[59,335,73],{"class":65},[59,337,338],{"class":69}," dataclass, field\n",[59,340,341],{"class":61,"line":79},[59,342,83],{"emptyLinePlaceholder":82},[59,344,345],{"class":61,"line":86},[59,346,90],{"class":89},[59,348,349,351,354],{"class":61,"line":93},[59,350,96],{"class":65},[59,352,353],{"class":89}," Cart",[59,355,102],{"class":69},[59,357,358,361,364,367,370],{"class":61,"line":105},[59,359,360],{"class":69},"    items: ",[59,362,363],{"class":111},"list",[59,365,366],{"class":65}," =",[59,368,369],{"class":69}," []                          ",[59,371,372],{"class":157},"# ValueError at class definition!\n",[59,374,375],{"class":61,"line":115},[59,376,83],{"emptyLinePlaceholder":82},[59,378,379],{"class":61,"line":123},[59,380,90],{"class":89},[59,382,383,385,387],{"class":61,"line":128},[59,384,96],{"class":65},[59,386,353],{"class":89},[59,388,102],{"class":69},[59,390,391,393,395,397,400,403,405,407,409],{"class":61,"line":151},[59,392,360],{"class":69},[59,394,363],{"class":111},[59,396,366],{"class":65},[59,398,399],{"class":69}," field(",[59,401,402],{"class":231},"default_factory",[59,404,134],{"class":65},[59,406,363],{"class":111},[59,408,177],{"class":69},[59,410,411],{"class":157},"# correct — fresh list per instance\n",[15,413,414,415,22,417,420],{},"The factory is a zero-argument callable (",[19,416,363],{},[19,418,419],{},"dict",", or a lambda) called once per new\ninstance.",[10,422,424,427],{"id":423},"post_init-for-derived-fields-and-validation",[209,425,426],{},"post_init"," for derived fields and validation",[15,429,430,432,433,322],{},[19,431,21],{}," is generated, so to run extra logic after the fields are set, define\n",[19,434,435],{},"__post_init__",[50,437,439],{"className":52,"code":438,"language":54,"meta":55,"style":55},"@dataclass\nclass Rectangle:\n    width: float\n    height: float\n    area: float = field(init=False)    # not a constructor argument\n\n    def __post_init__(self):\n        if self.width \u003C= 0:\n            raise ValueError(\"width must be positive\")\n        self.area = self.width * self.height\n",[19,440,441,445,454,462,469,495,499,510,529,544],{"__ignoreMap":55},[59,442,443],{"class":61,"line":62},[59,444,90],{"class":89},[59,446,447,449,452],{"class":61,"line":79},[59,448,96],{"class":65},[59,450,451],{"class":89}," Rectangle",[59,453,102],{"class":69},[59,455,456,459],{"class":61,"line":86},[59,457,458],{"class":69},"    width: ",[59,460,461],{"class":111},"float\n",[59,463,464,467],{"class":61,"line":93},[59,465,466],{"class":69},"    height: ",[59,468,461],{"class":111},[59,470,471,474,477,479,481,484,486,489,492],{"class":61,"line":105},[59,472,473],{"class":69},"    area: ",[59,475,476],{"class":111},"float",[59,478,366],{"class":65},[59,480,399],{"class":69},[59,482,483],{"class":231},"init",[59,485,134],{"class":65},[59,487,488],{"class":111},"False",[59,490,491],{"class":69},")    ",[59,493,494],{"class":157},"# not a constructor argument\n",[59,496,497],{"class":61,"line":115},[59,498,83],{"emptyLinePlaceholder":82},[59,500,501,504,507],{"class":61,"line":123},[59,502,503],{"class":65},"    def",[59,505,506],{"class":111}," __post_init__",[59,508,509],{"class":69},"(self):\n",[59,511,512,515,518,521,524,527],{"class":61,"line":128},[59,513,514],{"class":65},"        if",[59,516,517],{"class":111}," self",[59,519,520],{"class":69},".width ",[59,522,523],{"class":65},"\u003C=",[59,525,526],{"class":111}," 0",[59,528,102],{"class":69},[59,530,531,534,537,539,542],{"class":61,"line":151},[59,532,533],{"class":65},"            raise",[59,535,536],{"class":111}," ValueError",[59,538,228],{"class":69},[59,540,541],{"class":300},"\"width must be positive\"",[59,543,148],{"class":69},[59,545,546,549,552,554,556,558,561,563],{"class":61,"line":161},[59,547,548],{"class":111},"        self",[59,550,551],{"class":69},".area ",[59,553,134],{"class":65},[59,555,517],{"class":111},[59,557,520],{"class":69},[59,559,560],{"class":65},"*",[59,562,517],{"class":111},[59,564,565],{"class":69},".height\n",[15,567,568,571,572,575],{},[19,569,570],{},"field(init=False)"," keeps ",[19,573,574],{},"area"," out of the constructor signature so you can compute it here.",[10,577,579,582],{"id":578},"slots-smaller-faster-instances",[209,580,581],{},"slots"," — smaller, faster instances",[15,584,585,586,589,590,592],{},"By default each instance stores its attributes in a per-instance ",[19,587,588],{},"__dict__",", which is\nflexible but memory-hungry. ",[19,591,37],{}," replaces it with a fixed layout, cutting memory and\nspeeding attribute access — at the cost of being unable to add new attributes:",[50,594,596],{"className":52,"code":595,"language":54,"meta":55,"style":55},"class Point:\n    __slots__ = (\"x\", \"y\")\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\np = Point(1, 2)\np.z = 3          # AttributeError — not in __slots__\n",[19,597,598,606,626,636,654,658,674],{"__ignoreMap":55},[59,599,600,602,604],{"class":61,"line":62},[59,601,96],{"class":65},[59,603,99],{"class":89},[59,605,102],{"class":69},[59,607,608,611,613,616,619,621,624],{"class":61,"line":79},[59,609,610],{"class":111},"    __slots__",[59,612,366],{"class":65},[59,614,615],{"class":69}," (",[59,617,618],{"class":300},"\"x\"",[59,620,22],{"class":69},[59,622,623],{"class":300},"\"y\"",[59,625,148],{"class":69},[59,627,628,630,633],{"class":61,"line":86},[59,629,503],{"class":65},[59,631,632],{"class":111}," __init__",[59,634,635],{"class":69},"(self, x, y):\n",[59,637,638,640,643,646,649,651],{"class":61,"line":93},[59,639,548],{"class":111},[59,641,642],{"class":69},".x, ",[59,644,645],{"class":111},"self",[59,647,648],{"class":69},".y ",[59,650,134],{"class":65},[59,652,653],{"class":69}," x, y\n",[59,655,656],{"class":61,"line":105},[59,657,83],{"emptyLinePlaceholder":82},[59,659,660,662,664,666,668,670,672],{"class":61,"line":115},[59,661,131],{"class":69},[59,663,134],{"class":65},[59,665,137],{"class":69},[59,667,140],{"class":111},[59,669,22],{"class":69},[59,671,145],{"class":111},[59,673,148],{"class":69},[59,675,676,679,681,684],{"class":61,"line":123},[59,677,678],{"class":69},"p.z ",[59,680,134],{"class":65},[59,682,683],{"class":111}," 3",[59,685,686],{"class":157},"          # AttributeError — not in __slots__\n",[15,688,689,690,693,694,696],{},"Since Python 3.10 you can combine it with dataclasses via ",[19,691,692],{},"@dataclass(slots=True)",". Use\n",[19,695,37],{}," when you create huge numbers of small objects.",[10,698,700],{"id":699},"dataclass-vs-namedtuple-vs-namedtuple","dataclass vs namedtuple vs NamedTuple",[15,702,703],{},"All three model records; pick by mutability and behaviour:",[705,706,707,715],"ul",{},[708,709,710,714],"li",{},[209,711,712],{},[19,713,33],{}," — mutable by default (or frozen), supports methods, defaults, and\ninheritance. The general-purpose choice.",[708,716,717,726,727,730],{},[209,718,719,722,723],{},[19,720,721],{},"namedtuple"," \u002F ",[19,724,725],{},"typing.NamedTuple"," — immutable, ",[209,728,729],{},"tuple-based"," (indexable and\nunpackable), lighter weight. Best when you want tuple behaviour and immutability.",[50,732,734],{"className":52,"code":733,"language":54,"meta":55,"style":55},"# Want tuple unpacking and immutability with little code -> NamedTuple\n# Want mutability, methods, or rich behaviour            -> dataclass\n",[19,735,736,741],{"__ignoreMap":55},[59,737,738],{"class":61,"line":62},[59,739,740],{"class":157},"# Want tuple unpacking and immutability with little code -> NamedTuple\n",[59,742,743],{"class":61,"line":79},[59,744,745],{"class":157},"# Want mutability, methods, or rich behaviour            -> dataclass\n",[10,747,749],{"id":748},"recap","Recap",[15,751,752,754,755,22,757,26,759,761,762,764,765,768,769,772,773,775,776,778,779,783,784,786],{},[19,753,33],{}," generates ",[19,756,21],{},[19,758,25],{},[19,760,29],{}," from your annotations;\n",[19,763,197],{}," makes instances immutable ",[209,766,767],{},"and"," hashable. Mutable defaults are banned — use\n",[19,770,771],{},"field(default_factory=list)"," for a fresh object per instance, and ",[19,774,435],{}," for\nvalidation or derived fields (",[19,777,570],{},"). ",[209,780,781],{},[19,782,37],{}," drops the per-instance\n",[19,785,588],{}," to save memory and speed access for large object counts. Reach for a dataclass\nfor general records, and a named tuple when you want immutable, tuple-like behaviour.",[788,789,790],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":55,"searchDepth":79,"depth":79,"links":792},[793,794,795,796,797,799,801,802],{"id":12,"depth":79,"text":13},{"id":41,"depth":79,"text":42},{"id":201,"depth":79,"text":202},{"id":310,"depth":79,"text":311},{"id":423,"depth":79,"text":798},"post_init for derived fields and validation",{"id":578,"depth":79,"text":800},"slots — smaller, faster instances",{"id":699,"depth":79,"text":700},{"id":748,"depth":79,"text":749},"What @dataclass generates for you, frozen dataclasses, why mutable defaults need field(default_factory), what __slots__ buys you, and choosing between a dataclass and a namedtuple.","medium","md","Python",{},"\u002Fblog\u002Fpython-dataclasses-slots-explained","\u002Fpython\u002Foop\u002Fdataclasses-slots",{"title":5,"description":803},"blog\u002Fpython-dataclasses-slots-explained","Dataclasses & __slots__","Object-Oriented Programming","oop","2026-06-19","RAqsVyd0v341oeb8-y4E3e01jV8dbdrTc9Jt7j7SjKA",1782244094163]