[{"data":1,"prerenderedAt":2863},["ShallowReactive",2],{"page-\u002Fpython\u002F12-classes-and-objects":3},{"id":4,"title":5,"body":6,"description":35,"extension":2857,"meta":2858,"navigation":64,"path":2859,"seo":2860,"stem":2861,"__hash__":2862},"content\u002Fpython\u002F12-classes-and-objects.md","12 — Classes & Objects",{"type":7,"value":8,"toc":2837},"minimark",[9,13,26,719,726,743,850,864,868,1041,1065,1070,1268,1271,1275,1504,1533,1589,1596,1712,1718,1722,1725,1994,2023,2034,2157,2174,2178,2319,2329,2333,2416,2420,2515,2519,2522,2641,2765,2769,2833],[10,11,5],"h1",{"id":12},"_12-classes-objects",[14,15,17,18,22,23],"h2",{"id":16},"production-data-classes-dataclass-with-validation-and-__slots__","Production Data Classes — ",[19,20,21],"code",{},"@dataclass"," with Validation and ",[19,24,25],{},"__slots__",[27,28,30],"code-wrapper",{"language":29},"python",[31,32,36],"pre",{"className":33,"code":34,"language":29,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark","# ── Production: a validated, memory-efficient data class ──\n# Combines @dataclass (auto __init__\u002F__repr__\u002F__eq__) with __slots__ (no __dict__)\n# and __post_init__ for validation that can't be expressed in type hints alone.\n\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\n\n@dataclass(frozen=True, slots=True)\nclass User:\n    \"\"\"\n    Immutable user record — frozen=True makes it hashable (can be a dict key\u002Fset member),\n    slots=True eliminates __dict__ overhead (~40% memory reduction per instance).\n    \"\"\"\n    id: int\n    email: str\n    name: str\n    created_at: datetime = field(default_factory=datetime.now)  # mutable default → factory\n    roles: frozenset[str] = field(default_factory=frozenset)     # frozenset: immutable + hashable\n\n    def __post_init__(self):\n        # frozen=True means attributes can't be set normally — use object.__setattr__\n        # to work around the frozen restriction for validation-driven adjustments\n        if not self.email or \"@\" not in self.email:\n            raise ValueError(f\"invalid email: {self.email!r}\")\n        if self.id \u003C= 0:\n            raise ValueError(f\"id must be positive, got {self.id}\")\n\n# Frozen + slots dataclass is hashable → usable as dict keys and set members\nuser = User(id=1, email=\"ada@example.com\", \"name\"=\"Ada\")\nprint(user)   # User(id=1, email='ada@example.com', name='Ada', created_at=..., roles=frozenset())\n# user.email = \"x\"  # FrozenInstanceError — can't mutate a frozen dataclass\n\n# Can be used as a dict key (hashable because frozen + all fields hashable)\nuser_index = {user: \"admin\"}\n\n# ── Memory comparison: __dict__ vs __slots__ ──\nimport sys\n\n@dataclass\nclass UserDict:\n    id: int\n    email: str\n    name: str\n\n@dataclass(slots=True)\nclass UserSlots:\n    id: int\n    email: str\n    name: str\n\nud = UserDict(1, \"a@b.com\", \"Ada\")\nus = UserSlots(1, \"a@b.com\", \"Ada\")\nprint(f\"with __dict__:    {sys.getsizeof(ud) + sys.getsizeof(ud.__dict__)} bytes\")\nprint(f\"with __slots__:   {sys.getsizeof(us)} bytes\")\n# Typical: ~232 bytes vs ~64 bytes — 3.6× smaller, compounds at millions of instances\n","",[19,37,38,47,53,59,66,83,96,101,134,146,153,159,165,170,182,191,199,221,250,255,267,273,279,310,344,362,387,392,398,439,451,457,462,468,485,490,496,504,509,515,525,534,541,548,553,568,578,587,594,601,606,630,653,690,713],{"__ignoreMap":35},[39,40,43],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sdCPZ","# ── Production: a validated, memory-efficient data class ──\n",[39,48,50],{"class":41,"line":49},2,[39,51,52],{"class":45},"# Combines @dataclass (auto __init__\u002F__repr__\u002F__eq__) with __slots__ (no __dict__)\n",[39,54,56],{"class":41,"line":55},3,[39,57,58],{"class":45},"# and __post_init__ for validation that can't be expressed in type hints alone.\n",[39,60,62],{"class":41,"line":61},4,[39,63,65],{"emptyLinePlaceholder":64},true,"\n",[39,67,69,73,77,80],{"class":41,"line":68},5,[39,70,72],{"class":71},"svdQ7","from",[39,74,76],{"class":75},"ssxIu"," dataclasses ",[39,78,79],{"class":71},"import",[39,81,82],{"class":75}," dataclass, field\n",[39,84,86,88,91,93],{"class":41,"line":85},6,[39,87,72],{"class":71},[39,89,90],{"class":75}," datetime ",[39,92,79],{"class":71},[39,94,95],{"class":75}," datetime\n",[39,97,99],{"class":41,"line":98},7,[39,100,65],{"emptyLinePlaceholder":64},[39,102,104,107,110,114,117,121,124,127,129,131],{"class":41,"line":103},8,[39,105,21],{"class":106},"sIsaT",[39,108,109],{"class":75},"(",[39,111,113],{"class":112},"sCrzJ","frozen",[39,115,116],{"class":71},"=",[39,118,120],{"class":119},"snvgF","True",[39,122,123],{"class":75},", ",[39,125,126],{"class":112},"slots",[39,128,116],{"class":71},[39,130,120],{"class":119},[39,132,133],{"class":75},")\n",[39,135,137,140,143],{"class":41,"line":136},9,[39,138,139],{"class":71},"class",[39,141,142],{"class":106}," User",[39,144,145],{"class":75},":\n",[39,147,149],{"class":41,"line":148},10,[39,150,152],{"class":151},"sJ6F3","    \"\"\"\n",[39,154,156],{"class":41,"line":155},11,[39,157,158],{"class":151},"    Immutable user record — frozen=True makes it hashable (can be a dict key\u002Fset member),\n",[39,160,162],{"class":41,"line":161},12,[39,163,164],{"class":151},"    slots=True eliminates __dict__ overhead (~40% memory reduction per instance).\n",[39,166,168],{"class":41,"line":167},13,[39,169,152],{"class":151},[39,171,173,176,179],{"class":41,"line":172},14,[39,174,175],{"class":119},"    id",[39,177,178],{"class":75},": ",[39,180,181],{"class":119},"int\n",[39,183,185,188],{"class":41,"line":184},15,[39,186,187],{"class":75},"    email: ",[39,189,190],{"class":119},"str\n",[39,192,194,197],{"class":41,"line":193},16,[39,195,196],{"class":75},"    name: ",[39,198,190],{"class":119},[39,200,202,205,207,210,213,215,218],{"class":41,"line":201},17,[39,203,204],{"class":75},"    created_at: datetime ",[39,206,116],{"class":71},[39,208,209],{"class":75}," field(",[39,211,212],{"class":112},"default_factory",[39,214,116],{"class":71},[39,216,217],{"class":75},"datetime.now)  ",[39,219,220],{"class":45},"# mutable default → factory\n",[39,222,224,227,230,233,235,237,239,241,244,247],{"class":41,"line":223},18,[39,225,226],{"class":75},"    roles: frozenset[",[39,228,229],{"class":119},"str",[39,231,232],{"class":75},"] ",[39,234,116],{"class":71},[39,236,209],{"class":75},[39,238,212],{"class":112},[39,240,116],{"class":71},[39,242,243],{"class":119},"frozenset",[39,245,246],{"class":75},")     ",[39,248,249],{"class":45},"# frozenset: immutable + hashable\n",[39,251,253],{"class":41,"line":252},19,[39,254,65],{"emptyLinePlaceholder":64},[39,256,258,261,264],{"class":41,"line":257},20,[39,259,260],{"class":71},"    def",[39,262,263],{"class":119}," __post_init__",[39,265,266],{"class":75},"(self):\n",[39,268,270],{"class":41,"line":269},21,[39,271,272],{"class":45},"        # frozen=True means attributes can't be set normally — use object.__setattr__\n",[39,274,276],{"class":41,"line":275},22,[39,277,278],{"class":45},"        # to work around the frozen restriction for validation-driven adjustments\n",[39,280,282,285,288,291,294,297,300,302,305,307],{"class":41,"line":281},23,[39,283,284],{"class":71},"        if",[39,286,287],{"class":71}," not",[39,289,290],{"class":119}," self",[39,292,293],{"class":75},".email ",[39,295,296],{"class":71},"or",[39,298,299],{"class":151}," \"@\"",[39,301,287],{"class":71},[39,303,304],{"class":71}," in",[39,306,290],{"class":119},[39,308,309],{"class":75},".email:\n",[39,311,313,316,319,321,324,327,330,333,336,339,342],{"class":41,"line":312},24,[39,314,315],{"class":71},"            raise",[39,317,318],{"class":119}," ValueError",[39,320,109],{"class":75},[39,322,323],{"class":71},"f",[39,325,326],{"class":151},"\"invalid email: ",[39,328,329],{"class":119},"{self",[39,331,332],{"class":75},".email",[39,334,335],{"class":71},"!r",[39,337,338],{"class":119},"}",[39,340,341],{"class":151},"\"",[39,343,133],{"class":75},[39,345,347,349,351,354,357,360],{"class":41,"line":346},25,[39,348,284],{"class":71},[39,350,290],{"class":119},[39,352,353],{"class":75},".id ",[39,355,356],{"class":71},"\u003C=",[39,358,359],{"class":119}," 0",[39,361,145],{"class":75},[39,363,365,367,369,371,373,376,378,381,383,385],{"class":41,"line":364},26,[39,366,315],{"class":71},[39,368,318],{"class":119},[39,370,109],{"class":75},[39,372,323],{"class":71},[39,374,375],{"class":151},"\"id must be positive, got ",[39,377,329],{"class":119},[39,379,380],{"class":75},".id",[39,382,338],{"class":119},[39,384,341],{"class":151},[39,386,133],{"class":75},[39,388,390],{"class":41,"line":389},27,[39,391,65],{"emptyLinePlaceholder":64},[39,393,395],{"class":41,"line":394},28,[39,396,397],{"class":45},"# Frozen + slots dataclass is hashable → usable as dict keys and set members\n",[39,399,401,404,406,409,412,414,417,419,422,424,427,429,432,434,437],{"class":41,"line":400},29,[39,402,403],{"class":75},"user ",[39,405,116],{"class":71},[39,407,408],{"class":75}," User(",[39,410,411],{"class":112},"id",[39,413,116],{"class":71},[39,415,416],{"class":119},"1",[39,418,123],{"class":75},[39,420,421],{"class":112},"email",[39,423,116],{"class":71},[39,425,426],{"class":151},"\"ada@example.com\"",[39,428,123],{"class":75},[39,430,431],{"class":151},"\"name\"",[39,433,116],{"class":71},[39,435,436],{"class":151},"\"Ada\"",[39,438,133],{"class":75},[39,440,442,445,448],{"class":41,"line":441},30,[39,443,444],{"class":119},"print",[39,446,447],{"class":75},"(user)   ",[39,449,450],{"class":45},"# User(id=1, email='ada@example.com', name='Ada', created_at=..., roles=frozenset())\n",[39,452,454],{"class":41,"line":453},31,[39,455,456],{"class":45},"# user.email = \"x\"  # FrozenInstanceError — can't mutate a frozen dataclass\n",[39,458,460],{"class":41,"line":459},32,[39,461,65],{"emptyLinePlaceholder":64},[39,463,465],{"class":41,"line":464},33,[39,466,467],{"class":45},"# Can be used as a dict key (hashable because frozen + all fields hashable)\n",[39,469,471,474,476,479,482],{"class":41,"line":470},34,[39,472,473],{"class":75},"user_index ",[39,475,116],{"class":71},[39,477,478],{"class":75}," {user: ",[39,480,481],{"class":151},"\"admin\"",[39,483,484],{"class":75},"}\n",[39,486,488],{"class":41,"line":487},35,[39,489,65],{"emptyLinePlaceholder":64},[39,491,493],{"class":41,"line":492},36,[39,494,495],{"class":45},"# ── Memory comparison: __dict__ vs __slots__ ──\n",[39,497,499,501],{"class":41,"line":498},37,[39,500,79],{"class":71},[39,502,503],{"class":75}," sys\n",[39,505,507],{"class":41,"line":506},38,[39,508,65],{"emptyLinePlaceholder":64},[39,510,512],{"class":41,"line":511},39,[39,513,514],{"class":106},"@dataclass\n",[39,516,518,520,523],{"class":41,"line":517},40,[39,519,139],{"class":71},[39,521,522],{"class":106}," UserDict",[39,524,145],{"class":75},[39,526,528,530,532],{"class":41,"line":527},41,[39,529,175],{"class":119},[39,531,178],{"class":75},[39,533,181],{"class":119},[39,535,537,539],{"class":41,"line":536},42,[39,538,187],{"class":75},[39,540,190],{"class":119},[39,542,544,546],{"class":41,"line":543},43,[39,545,196],{"class":75},[39,547,190],{"class":119},[39,549,551],{"class":41,"line":550},44,[39,552,65],{"emptyLinePlaceholder":64},[39,554,556,558,560,562,564,566],{"class":41,"line":555},45,[39,557,21],{"class":106},[39,559,109],{"class":75},[39,561,126],{"class":112},[39,563,116],{"class":71},[39,565,120],{"class":119},[39,567,133],{"class":75},[39,569,571,573,576],{"class":41,"line":570},46,[39,572,139],{"class":71},[39,574,575],{"class":106}," UserSlots",[39,577,145],{"class":75},[39,579,581,583,585],{"class":41,"line":580},47,[39,582,175],{"class":119},[39,584,178],{"class":75},[39,586,181],{"class":119},[39,588,590,592],{"class":41,"line":589},48,[39,591,187],{"class":75},[39,593,190],{"class":119},[39,595,597,599],{"class":41,"line":596},49,[39,598,196],{"class":75},[39,600,190],{"class":119},[39,602,604],{"class":41,"line":603},50,[39,605,65],{"emptyLinePlaceholder":64},[39,607,609,612,614,617,619,621,624,626,628],{"class":41,"line":608},51,[39,610,611],{"class":75},"ud ",[39,613,116],{"class":71},[39,615,616],{"class":75}," UserDict(",[39,618,416],{"class":119},[39,620,123],{"class":75},[39,622,623],{"class":151},"\"a@b.com\"",[39,625,123],{"class":75},[39,627,436],{"class":151},[39,629,133],{"class":75},[39,631,633,636,638,641,643,645,647,649,651],{"class":41,"line":632},52,[39,634,635],{"class":75},"us ",[39,637,116],{"class":71},[39,639,640],{"class":75}," UserSlots(",[39,642,416],{"class":119},[39,644,123],{"class":75},[39,646,623],{"class":151},[39,648,123],{"class":75},[39,650,436],{"class":151},[39,652,133],{"class":75},[39,654,656,658,660,662,665,668,671,674,677,680,683,685,688],{"class":41,"line":655},53,[39,657,444],{"class":119},[39,659,109],{"class":75},[39,661,323],{"class":71},[39,663,664],{"class":151},"\"with __dict__:    ",[39,666,667],{"class":119},"{",[39,669,670],{"class":75},"sys.getsizeof(ud) ",[39,672,673],{"class":71},"+",[39,675,676],{"class":75}," sys.getsizeof(ud.",[39,678,679],{"class":119},"__dict__",[39,681,682],{"class":75},")",[39,684,338],{"class":119},[39,686,687],{"class":151}," bytes\"",[39,689,133],{"class":75},[39,691,693,695,697,699,702,704,707,709,711],{"class":41,"line":692},54,[39,694,444],{"class":119},[39,696,109],{"class":75},[39,698,323],{"class":71},[39,700,701],{"class":151},"\"with __slots__:   ",[39,703,667],{"class":119},[39,705,706],{"class":75},"sys.getsizeof(us)",[39,708,338],{"class":119},[39,710,687],{"class":151},[39,712,133],{"class":75},[39,714,716],{"class":41,"line":715},55,[39,717,718],{"class":45},"# Typical: ~232 bytes vs ~64 bytes — 3.6× smaller, compounds at millions of instances\n",[14,720,722,725],{"id":721},"self-is-not-magic",[19,723,724],{},"self"," Is Not Magic",[727,728,729,731,732,734,735,738,739,742],"p",{},[19,730,724],{}," is just the first parameter of an instance method, conventionally named ",[19,733,724],{}," — Python passes the instance automatically when you call ",[19,736,737],{},"obj.method(...)",", which is sugar for ",[19,740,741],{},"ClassName.method(obj, ...)",".",[27,744,745],{"language":29},[31,746,748],{"className":33,"code":747,"language":29,"meta":35,"style":35},"class Counter:\n    def __init__(self):\n        self.count = 0\n\n    def increment(self):\n        self.count += 1\n        return self.count\n\nc = Counter()\nprint(c.increment())        # 1 — sugar for Counter.increment(c)\nprint(Counter.increment(c))   # 2 — identical, explicit form\n",[19,749,750,759,768,781,785,794,806,816,820,830,840],{"__ignoreMap":35},[39,751,752,754,757],{"class":41,"line":42},[39,753,139],{"class":71},[39,755,756],{"class":106}," Counter",[39,758,145],{"class":75},[39,760,761,763,766],{"class":41,"line":49},[39,762,260],{"class":71},[39,764,765],{"class":119}," __init__",[39,767,266],{"class":75},[39,769,770,773,776,778],{"class":41,"line":55},[39,771,772],{"class":119},"        self",[39,774,775],{"class":75},".count ",[39,777,116],{"class":71},[39,779,780],{"class":119}," 0\n",[39,782,783],{"class":41,"line":61},[39,784,65],{"emptyLinePlaceholder":64},[39,786,787,789,792],{"class":41,"line":68},[39,788,260],{"class":71},[39,790,791],{"class":106}," increment",[39,793,266],{"class":75},[39,795,796,798,800,803],{"class":41,"line":85},[39,797,772],{"class":119},[39,799,775],{"class":75},[39,801,802],{"class":71},"+=",[39,804,805],{"class":119}," 1\n",[39,807,808,811,813],{"class":41,"line":98},[39,809,810],{"class":71},"        return",[39,812,290],{"class":119},[39,814,815],{"class":75},".count\n",[39,817,818],{"class":41,"line":103},[39,819,65],{"emptyLinePlaceholder":64},[39,821,822,825,827],{"class":41,"line":136},[39,823,824],{"class":75},"c ",[39,826,116],{"class":71},[39,828,829],{"class":75}," Counter()\n",[39,831,832,834,837],{"class":41,"line":148},[39,833,444],{"class":119},[39,835,836],{"class":75},"(c.increment())        ",[39,838,839],{"class":45},"# 1 — sugar for Counter.increment(c)\n",[39,841,842,844,847],{"class":41,"line":155},[39,843,444],{"class":119},[39,845,846],{"class":75},"(Counter.increment(c))   ",[39,848,849],{"class":45},"# 2 — identical, explicit form\n",[727,851,852,853,855,856,859,860,863],{},"There is nothing special about the name ",[19,854,724],{}," — it's a convention so strong that violating it (naming it ",[19,857,858],{},"this"," or ",[19,861,862],{},"s",") is considered a serious style violation, but the language itself doesn't enforce it.",[14,865,867],{"id":866},"instance-attributes-vs-class-attributes","Instance Attributes vs Class Attributes",[27,869,870],{"language":29},[31,871,873],{"className":33,"code":872,"language":29,"meta":35,"style":35},"class Employee:\n    company = \"Acme Corp\"          # CLASS attribute — shared by ALL instances\n\n    def __init__(self, name):\n        self.name = name              # INSTANCE attribute — unique per object\n\nalice = Employee(\"Alice\")\nbob = Employee(\"Bob\")\n\nprint(alice.company, bob.company)   # Acme Corp Acme Corp — same object, shared\nprint(Employee.company)               # Acme Corp\n\nEmployee.company = \"NewCo\"              # changes it for ALL instances (and future ones)\nprint(alice.company, bob.company)         # NewCo NewCo\n\nalice.company = \"Alice's Startup\"           # creates a NEW instance attribute on alice,\nprint(alice.company, bob.company)             # SHADOWING the class attribute — doesn't touch it\n# Alice's Startup NewCo\n",[19,874,875,884,897,901,910,925,929,944,958,962,972,982,986,999,1009,1013,1026,1036],{"__ignoreMap":35},[39,876,877,879,882],{"class":41,"line":42},[39,878,139],{"class":71},[39,880,881],{"class":106}," Employee",[39,883,145],{"class":75},[39,885,886,889,891,894],{"class":41,"line":49},[39,887,888],{"class":75},"    company ",[39,890,116],{"class":71},[39,892,893],{"class":151}," \"Acme Corp\"",[39,895,896],{"class":45},"          # CLASS attribute — shared by ALL instances\n",[39,898,899],{"class":41,"line":55},[39,900,65],{"emptyLinePlaceholder":64},[39,902,903,905,907],{"class":41,"line":61},[39,904,260],{"class":71},[39,906,765],{"class":119},[39,908,909],{"class":75},"(self, name):\n",[39,911,912,914,917,919,922],{"class":41,"line":68},[39,913,772],{"class":119},[39,915,916],{"class":75},".name ",[39,918,116],{"class":71},[39,920,921],{"class":75}," name              ",[39,923,924],{"class":45},"# INSTANCE attribute — unique per object\n",[39,926,927],{"class":41,"line":85},[39,928,65],{"emptyLinePlaceholder":64},[39,930,931,934,936,939,942],{"class":41,"line":98},[39,932,933],{"class":75},"alice ",[39,935,116],{"class":71},[39,937,938],{"class":75}," Employee(",[39,940,941],{"class":151},"\"Alice\"",[39,943,133],{"class":75},[39,945,946,949,951,953,956],{"class":41,"line":103},[39,947,948],{"class":75},"bob ",[39,950,116],{"class":71},[39,952,938],{"class":75},[39,954,955],{"class":151},"\"Bob\"",[39,957,133],{"class":75},[39,959,960],{"class":41,"line":136},[39,961,65],{"emptyLinePlaceholder":64},[39,963,964,966,969],{"class":41,"line":148},[39,965,444],{"class":119},[39,967,968],{"class":75},"(alice.company, bob.company)   ",[39,970,971],{"class":45},"# Acme Corp Acme Corp — same object, shared\n",[39,973,974,976,979],{"class":41,"line":155},[39,975,444],{"class":119},[39,977,978],{"class":75},"(Employee.company)               ",[39,980,981],{"class":45},"# Acme Corp\n",[39,983,984],{"class":41,"line":161},[39,985,65],{"emptyLinePlaceholder":64},[39,987,988,991,993,996],{"class":41,"line":167},[39,989,990],{"class":75},"Employee.company ",[39,992,116],{"class":71},[39,994,995],{"class":151}," \"NewCo\"",[39,997,998],{"class":45},"              # changes it for ALL instances (and future ones)\n",[39,1000,1001,1003,1006],{"class":41,"line":172},[39,1002,444],{"class":119},[39,1004,1005],{"class":75},"(alice.company, bob.company)         ",[39,1007,1008],{"class":45},"# NewCo NewCo\n",[39,1010,1011],{"class":41,"line":184},[39,1012,65],{"emptyLinePlaceholder":64},[39,1014,1015,1018,1020,1023],{"class":41,"line":193},[39,1016,1017],{"class":75},"alice.company ",[39,1019,116],{"class":71},[39,1021,1022],{"class":151}," \"Alice's Startup\"",[39,1024,1025],{"class":45},"           # creates a NEW instance attribute on alice,\n",[39,1027,1028,1030,1033],{"class":41,"line":201},[39,1029,444],{"class":119},[39,1031,1032],{"class":75},"(alice.company, bob.company)             ",[39,1034,1035],{"class":45},"# SHADOWING the class attribute — doesn't touch it\n",[39,1037,1038],{"class":41,"line":223},[39,1039,1040],{"class":45},"# Alice's Startup NewCo\n",[727,1042,1043,1044,1047,1048,1051,1052,1055,1056,1058,1059,1061,1062,1064],{},"Assigning ",[19,1045,1046],{},"alice.company = ..."," never modifies ",[19,1049,1050],{},"Employee.company"," — it creates a new entry in ",[19,1053,1054],{},"alice","'s own ",[19,1057,679],{}," that shadows the class attribute for lookups on ",[19,1060,1054],{}," specifically. This is the same lookup chain (instance ",[19,1063,679],{}," first, then class, then base classes via MRO) that chapter 13 covers for inheritance.",[1066,1067,1069],"h3",{"id":1068},"the-mutable-class-attribute-trap","The mutable class attribute trap",[27,1071,1072],{"language":29},[31,1073,1075],{"className":33,"code":1074,"language":29,"meta":35,"style":35},"# WRONG — a mutable class attribute is SHARED and mutated in place by every instance\nclass ShoppingCart:\n    items = []                    # class attribute — ONE list shared by all carts!\n\n    def add(self, item):\n        self.items.append(item)     # mutates the SHARED list, doesn't create a new one\n\ncart_a = ShoppingCart()\ncart_b = ShoppingCart()\ncart_a.add(\"apple\")\nprint(cart_b.items)   # ['apple'] — leaked into cart_b, which never touched cart_a!\n\n# RIGHT — initialize mutable state in __init__, so each instance gets its own\nclass ShoppingCartFixed:\n    def __init__(self):\n        self.items = []              # instance attribute — fresh list per instance\n\ncart_c = ShoppingCartFixed()\ncart_d = ShoppingCartFixed()\ncart_c.add = lambda item: cart_c.items.append(item)\ncart_c.add(\"banana\")\nprint(cart_d.items)   # [] — correctly isolated\n",[19,1076,1077,1082,1091,1104,1108,1118,1128,1132,1142,1151,1161,1171,1175,1180,1189,1197,1212,1216,1226,1235,1248,1258],{"__ignoreMap":35},[39,1078,1079],{"class":41,"line":42},[39,1080,1081],{"class":45},"# WRONG — a mutable class attribute is SHARED and mutated in place by every instance\n",[39,1083,1084,1086,1089],{"class":41,"line":49},[39,1085,139],{"class":71},[39,1087,1088],{"class":106}," ShoppingCart",[39,1090,145],{"class":75},[39,1092,1093,1096,1098,1101],{"class":41,"line":55},[39,1094,1095],{"class":75},"    items ",[39,1097,116],{"class":71},[39,1099,1100],{"class":75}," []                    ",[39,1102,1103],{"class":45},"# class attribute — ONE list shared by all carts!\n",[39,1105,1106],{"class":41,"line":61},[39,1107,65],{"emptyLinePlaceholder":64},[39,1109,1110,1112,1115],{"class":41,"line":68},[39,1111,260],{"class":71},[39,1113,1114],{"class":106}," add",[39,1116,1117],{"class":75},"(self, item):\n",[39,1119,1120,1122,1125],{"class":41,"line":85},[39,1121,772],{"class":119},[39,1123,1124],{"class":75},".items.append(item)     ",[39,1126,1127],{"class":45},"# mutates the SHARED list, doesn't create a new one\n",[39,1129,1130],{"class":41,"line":98},[39,1131,65],{"emptyLinePlaceholder":64},[39,1133,1134,1137,1139],{"class":41,"line":103},[39,1135,1136],{"class":75},"cart_a ",[39,1138,116],{"class":71},[39,1140,1141],{"class":75}," ShoppingCart()\n",[39,1143,1144,1147,1149],{"class":41,"line":136},[39,1145,1146],{"class":75},"cart_b ",[39,1148,116],{"class":71},[39,1150,1141],{"class":75},[39,1152,1153,1156,1159],{"class":41,"line":148},[39,1154,1155],{"class":75},"cart_a.add(",[39,1157,1158],{"class":151},"\"apple\"",[39,1160,133],{"class":75},[39,1162,1163,1165,1168],{"class":41,"line":155},[39,1164,444],{"class":119},[39,1166,1167],{"class":75},"(cart_b.items)   ",[39,1169,1170],{"class":45},"# ['apple'] — leaked into cart_b, which never touched cart_a!\n",[39,1172,1173],{"class":41,"line":161},[39,1174,65],{"emptyLinePlaceholder":64},[39,1176,1177],{"class":41,"line":167},[39,1178,1179],{"class":45},"# RIGHT — initialize mutable state in __init__, so each instance gets its own\n",[39,1181,1182,1184,1187],{"class":41,"line":172},[39,1183,139],{"class":71},[39,1185,1186],{"class":106}," ShoppingCartFixed",[39,1188,145],{"class":75},[39,1190,1191,1193,1195],{"class":41,"line":184},[39,1192,260],{"class":71},[39,1194,765],{"class":119},[39,1196,266],{"class":75},[39,1198,1199,1201,1204,1206,1209],{"class":41,"line":193},[39,1200,772],{"class":119},[39,1202,1203],{"class":75},".items ",[39,1205,116],{"class":71},[39,1207,1208],{"class":75}," []              ",[39,1210,1211],{"class":45},"# instance attribute — fresh list per instance\n",[39,1213,1214],{"class":41,"line":201},[39,1215,65],{"emptyLinePlaceholder":64},[39,1217,1218,1221,1223],{"class":41,"line":223},[39,1219,1220],{"class":75},"cart_c ",[39,1222,116],{"class":71},[39,1224,1225],{"class":75}," ShoppingCartFixed()\n",[39,1227,1228,1231,1233],{"class":41,"line":252},[39,1229,1230],{"class":75},"cart_d ",[39,1232,116],{"class":71},[39,1234,1225],{"class":75},[39,1236,1237,1240,1242,1245],{"class":41,"line":257},[39,1238,1239],{"class":75},"cart_c.add ",[39,1241,116],{"class":71},[39,1243,1244],{"class":71}," lambda",[39,1246,1247],{"class":75}," item: cart_c.items.append(item)\n",[39,1249,1250,1253,1256],{"class":41,"line":269},[39,1251,1252],{"class":75},"cart_c.add(",[39,1254,1255],{"class":151},"\"banana\"",[39,1257,133],{"class":75},[39,1259,1260,1262,1265],{"class":41,"line":275},[39,1261,444],{"class":119},[39,1263,1264],{"class":75},"(cart_d.items)   ",[39,1266,1267],{"class":45},"# [] — correctly isolated\n",[727,1269,1270],{},"This is the class-level cousin of the mutable-default-argument trap from chapter 05 — the underlying mechanism is identical: an object created once, at definition time, and then shared everywhere it's referenced instead of being freshly created per use.",[14,1272,1274],{"id":1273},"instance-methods-class-methods-and-static-methods","Instance Methods, Class Methods, and Static Methods",[27,1276,1277],{"language":29},[31,1278,1280],{"className":33,"code":1279,"language":29,"meta":35,"style":35},"class Pizza:\n    def __init__(self, toppings):\n        self.toppings = toppings\n\n    def describe(self):                       # instance method — needs an instance\n        return f\"Pizza with {', '.join(self.toppings)}\"\n\n    @classmethod\n    def margherita(cls):                       # classmethod — receives the CLASS, not an instance\n        return cls([\"mozzarella\", \"basil\"])       # cls(...) so subclasses construct correctly\n\n    @staticmethod\n    def is_valid_topping(topping):                # staticmethod — no self, no cls, just a namespaced function\n        return topping.lower() not in {\"pineapple\"}\n\np = Pizza.margherita()\nprint(p.describe())                    # Pizza with mozzarella, basil\nprint(Pizza.is_valid_topping(\"ham\"))     # True\nprint(Pizza.is_valid_topping(\"pineapple\"))  # False (a controversial but valid design opinion)\n",[19,1281,1282,1291,1300,1312,1316,1329,1357,1361,1369,1382,1406,1410,1417,1430,1450,1454,1464,1474,1490],{"__ignoreMap":35},[39,1283,1284,1286,1289],{"class":41,"line":42},[39,1285,139],{"class":71},[39,1287,1288],{"class":106}," Pizza",[39,1290,145],{"class":75},[39,1292,1293,1295,1297],{"class":41,"line":49},[39,1294,260],{"class":71},[39,1296,765],{"class":119},[39,1298,1299],{"class":75},"(self, toppings):\n",[39,1301,1302,1304,1307,1309],{"class":41,"line":55},[39,1303,772],{"class":119},[39,1305,1306],{"class":75},".toppings ",[39,1308,116],{"class":71},[39,1310,1311],{"class":75}," toppings\n",[39,1313,1314],{"class":41,"line":61},[39,1315,65],{"emptyLinePlaceholder":64},[39,1317,1318,1320,1323,1326],{"class":41,"line":68},[39,1319,260],{"class":71},[39,1321,1322],{"class":106}," describe",[39,1324,1325],{"class":75},"(self):                       ",[39,1327,1328],{"class":45},"# instance method — needs an instance\n",[39,1330,1331,1333,1336,1339,1341,1344,1347,1349,1352,1354],{"class":41,"line":85},[39,1332,810],{"class":71},[39,1334,1335],{"class":71}," f",[39,1337,1338],{"class":151},"\"Pizza with ",[39,1340,667],{"class":119},[39,1342,1343],{"class":151},"', '",[39,1345,1346],{"class":75},".join(",[39,1348,724],{"class":119},[39,1350,1351],{"class":75},".toppings)",[39,1353,338],{"class":119},[39,1355,1356],{"class":151},"\"\n",[39,1358,1359],{"class":41,"line":98},[39,1360,65],{"emptyLinePlaceholder":64},[39,1362,1363,1366],{"class":41,"line":103},[39,1364,1365],{"class":106},"    @",[39,1367,1368],{"class":119},"classmethod\n",[39,1370,1371,1373,1376,1379],{"class":41,"line":136},[39,1372,260],{"class":71},[39,1374,1375],{"class":106}," margherita",[39,1377,1378],{"class":75},"(cls):                       ",[39,1380,1381],{"class":45},"# classmethod — receives the CLASS, not an instance\n",[39,1383,1384,1386,1389,1392,1395,1397,1400,1403],{"class":41,"line":148},[39,1385,810],{"class":71},[39,1387,1388],{"class":119}," cls",[39,1390,1391],{"class":75},"([",[39,1393,1394],{"class":151},"\"mozzarella\"",[39,1396,123],{"class":75},[39,1398,1399],{"class":151},"\"basil\"",[39,1401,1402],{"class":75},"])       ",[39,1404,1405],{"class":45},"# cls(...) so subclasses construct correctly\n",[39,1407,1408],{"class":41,"line":155},[39,1409,65],{"emptyLinePlaceholder":64},[39,1411,1412,1414],{"class":41,"line":161},[39,1413,1365],{"class":106},[39,1415,1416],{"class":119},"staticmethod\n",[39,1418,1419,1421,1424,1427],{"class":41,"line":167},[39,1420,260],{"class":71},[39,1422,1423],{"class":106}," is_valid_topping",[39,1425,1426],{"class":75},"(topping):                ",[39,1428,1429],{"class":45},"# staticmethod — no self, no cls, just a namespaced function\n",[39,1431,1432,1434,1437,1440,1442,1445,1448],{"class":41,"line":172},[39,1433,810],{"class":71},[39,1435,1436],{"class":75}," topping.lower() ",[39,1438,1439],{"class":71},"not",[39,1441,304],{"class":71},[39,1443,1444],{"class":75}," {",[39,1446,1447],{"class":151},"\"pineapple\"",[39,1449,484],{"class":75},[39,1451,1452],{"class":41,"line":184},[39,1453,65],{"emptyLinePlaceholder":64},[39,1455,1456,1459,1461],{"class":41,"line":193},[39,1457,1458],{"class":75},"p ",[39,1460,116],{"class":71},[39,1462,1463],{"class":75}," Pizza.margherita()\n",[39,1465,1466,1468,1471],{"class":41,"line":201},[39,1467,444],{"class":119},[39,1469,1470],{"class":75},"(p.describe())                    ",[39,1472,1473],{"class":45},"# Pizza with mozzarella, basil\n",[39,1475,1476,1478,1481,1484,1487],{"class":41,"line":223},[39,1477,444],{"class":119},[39,1479,1480],{"class":75},"(Pizza.is_valid_topping(",[39,1482,1483],{"class":151},"\"ham\"",[39,1485,1486],{"class":75},"))     ",[39,1488,1489],{"class":45},"# True\n",[39,1491,1492,1494,1496,1498,1501],{"class":41,"line":252},[39,1493,444],{"class":119},[39,1495,1480],{"class":75},[39,1497,1447],{"class":151},[39,1499,1500],{"class":75},"))  ",[39,1502,1503],{"class":45},"# False (a controversial but valid design opinion)\n",[727,1505,1506,1509,1510,1514,1515,123,1518,123,1521,1524,1525,1528,1529,1532],{},[19,1507,1508],{},"@classmethod"," is the idiomatic way to write ",[1511,1512,1513],"strong",{},"alternative constructors"," (",[19,1516,1517],{},"from_json",[19,1519,1520],{},"from_config",[19,1522,1523],{},"margherita",") — using ",[19,1526,1527],{},"cls(...)"," instead of hardcoding ",[19,1530,1531],{},"Pizza(...)"," means subclasses inherit a correctly-typed constructor for free:",[27,1534,1535],{"language":29},[31,1536,1538],{"className":33,"code":1537,"language":29,"meta":35,"style":35},"class StuffedCrustPizza(Pizza):\n    pass\n\nsc = StuffedCrustPizza.margherita()\nprint(type(sc))   # \u003Cclass '__main__.StuffedCrustPizza'> — NOT Pizza, thanks to cls(...)\n",[19,1539,1540,1555,1560,1564,1574],{"__ignoreMap":35},[39,1541,1542,1544,1547,1549,1552],{"class":41,"line":42},[39,1543,139],{"class":71},[39,1545,1546],{"class":106}," StuffedCrustPizza",[39,1548,109],{"class":75},[39,1550,1551],{"class":106},"Pizza",[39,1553,1554],{"class":75},"):\n",[39,1556,1557],{"class":41,"line":49},[39,1558,1559],{"class":71},"    pass\n",[39,1561,1562],{"class":41,"line":55},[39,1563,65],{"emptyLinePlaceholder":64},[39,1565,1566,1569,1571],{"class":41,"line":61},[39,1567,1568],{"class":75},"sc ",[39,1570,116],{"class":71},[39,1572,1573],{"class":75}," StuffedCrustPizza.margherita()\n",[39,1575,1576,1578,1580,1583,1586],{"class":41,"line":68},[39,1577,444],{"class":119},[39,1579,109],{"class":75},[39,1581,1582],{"class":119},"type",[39,1584,1585],{"class":75},"(sc))   ",[39,1587,1588],{"class":45},"# \u003Cclass '__main__.StuffedCrustPizza'> — NOT Pizza, thanks to cls(...)\n",[14,1590,1592,1593,1595],{"id":1591},"every-instance-has-a-__dict__-usually","Every Instance Has a ",[19,1594,679],{}," (Usually)",[27,1597,1598],{"language":29},[31,1599,1601],{"className":33,"code":1600,"language":29,"meta":35,"style":35},"class Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\np = Point(1, 2)\nprint(p.__dict__)   # {'x': 1, 'y': 2}\n\np.z = 99            # you can add attributes NOT declared in __init__ at all\nprint(p.__dict__)     # {'x': 1, 'y': 2, 'z': 99}\n",[19,1602,1603,1612,1621,1633,1645,1649,1667,1682,1686,1699],{"__ignoreMap":35},[39,1604,1605,1607,1610],{"class":41,"line":42},[39,1606,139],{"class":71},[39,1608,1609],{"class":106}," Point",[39,1611,145],{"class":75},[39,1613,1614,1616,1618],{"class":41,"line":49},[39,1615,260],{"class":71},[39,1617,765],{"class":119},[39,1619,1620],{"class":75},"(self, x, y):\n",[39,1622,1623,1625,1628,1630],{"class":41,"line":55},[39,1624,772],{"class":119},[39,1626,1627],{"class":75},".x ",[39,1629,116],{"class":71},[39,1631,1632],{"class":75}," x\n",[39,1634,1635,1637,1640,1642],{"class":41,"line":61},[39,1636,772],{"class":119},[39,1638,1639],{"class":75},".y ",[39,1641,116],{"class":71},[39,1643,1644],{"class":75}," y\n",[39,1646,1647],{"class":41,"line":68},[39,1648,65],{"emptyLinePlaceholder":64},[39,1650,1651,1653,1655,1658,1660,1662,1665],{"class":41,"line":85},[39,1652,1458],{"class":75},[39,1654,116],{"class":71},[39,1656,1657],{"class":75}," Point(",[39,1659,416],{"class":119},[39,1661,123],{"class":75},[39,1663,1664],{"class":119},"2",[39,1666,133],{"class":75},[39,1668,1669,1671,1674,1676,1679],{"class":41,"line":98},[39,1670,444],{"class":119},[39,1672,1673],{"class":75},"(p.",[39,1675,679],{"class":119},[39,1677,1678],{"class":75},")   ",[39,1680,1681],{"class":45},"# {'x': 1, 'y': 2}\n",[39,1683,1684],{"class":41,"line":103},[39,1685,65],{"emptyLinePlaceholder":64},[39,1687,1688,1691,1693,1696],{"class":41,"line":136},[39,1689,1690],{"class":75},"p.z ",[39,1692,116],{"class":71},[39,1694,1695],{"class":119}," 99",[39,1697,1698],{"class":45},"            # you can add attributes NOT declared in __init__ at all\n",[39,1700,1701,1703,1705,1707,1709],{"class":41,"line":148},[39,1702,444],{"class":119},[39,1704,1673],{"class":75},[39,1706,679],{"class":119},[39,1708,246],{"class":75},[39,1710,1711],{"class":45},"# {'x': 1, 'y': 2, 'z': 99}\n",[727,1713,1714,1715,1717],{},"Unlike statically-typed languages, Python objects are open by default — any code holding a reference to an instance can add, remove, or overwrite attributes freely, unless the class opts out with ",[19,1716,25],{}," (chapter 15), which trades this flexibility for memory savings and typo protection.",[14,1719,1721],{"id":1720},"dunder-methods-a-first-look","Dunder Methods: A First Look",[727,1723,1724],{},"Chapter 14 covers these in depth; a brief overview here connects classes to the operators and built-ins you already know.",[27,1726,1727],{"language":29},[31,1728,1730],{"className":33,"code":1729,"language":29,"meta":35,"style":35},"class Vector:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n    def __repr__(self):                        # unambiguous, developer-facing representation\n        return f\"Vector({self.x}, {self.y})\"\n\n    def __eq__(self, other):                   # powers ==\n        return isinstance(other, Vector) and self.x == other.x and self.y == other.y\n\n    def __add__(self, other):                  # powers +\n        return Vector(self.x + other.x, self.y + other.y)\n\nv1 = Vector(1, 2)\nv2 = Vector(3, 4)\nprint(v1 + v2)          # Vector(4, 6) — uses __add__ and __repr__\nprint(v1 == Vector(1, 2))   # True — uses __eq__\nprint([v1, v2])                # [Vector(1, 2), Vector(3, 4)] — repr used inside containers\n",[19,1731,1732,1741,1749,1759,1769,1773,1786,1814,1818,1831,1865,1869,1882,1907,1911,1928,1947,1962,1984],{"__ignoreMap":35},[39,1733,1734,1736,1739],{"class":41,"line":42},[39,1735,139],{"class":71},[39,1737,1738],{"class":106}," Vector",[39,1740,145],{"class":75},[39,1742,1743,1745,1747],{"class":41,"line":49},[39,1744,260],{"class":71},[39,1746,765],{"class":119},[39,1748,1620],{"class":75},[39,1750,1751,1753,1755,1757],{"class":41,"line":55},[39,1752,772],{"class":119},[39,1754,1627],{"class":75},[39,1756,116],{"class":71},[39,1758,1632],{"class":75},[39,1760,1761,1763,1765,1767],{"class":41,"line":61},[39,1762,772],{"class":119},[39,1764,1639],{"class":75},[39,1766,116],{"class":71},[39,1768,1644],{"class":75},[39,1770,1771],{"class":41,"line":68},[39,1772,65],{"emptyLinePlaceholder":64},[39,1774,1775,1777,1780,1783],{"class":41,"line":85},[39,1776,260],{"class":71},[39,1778,1779],{"class":119}," __repr__",[39,1781,1782],{"class":75},"(self):                        ",[39,1784,1785],{"class":45},"# unambiguous, developer-facing representation\n",[39,1787,1788,1790,1792,1795,1797,1800,1802,1804,1806,1809,1811],{"class":41,"line":98},[39,1789,810],{"class":71},[39,1791,1335],{"class":71},[39,1793,1794],{"class":151},"\"Vector(",[39,1796,329],{"class":119},[39,1798,1799],{"class":75},".x",[39,1801,338],{"class":119},[39,1803,123],{"class":151},[39,1805,329],{"class":119},[39,1807,1808],{"class":75},".y",[39,1810,338],{"class":119},[39,1812,1813],{"class":151},")\"\n",[39,1815,1816],{"class":41,"line":103},[39,1817,65],{"emptyLinePlaceholder":64},[39,1819,1820,1822,1825,1828],{"class":41,"line":136},[39,1821,260],{"class":71},[39,1823,1824],{"class":119}," __eq__",[39,1826,1827],{"class":75},"(self, other):                   ",[39,1829,1830],{"class":45},"# powers ==\n",[39,1832,1833,1835,1838,1841,1844,1846,1848,1851,1854,1856,1858,1860,1862],{"class":41,"line":148},[39,1834,810],{"class":71},[39,1836,1837],{"class":119}," isinstance",[39,1839,1840],{"class":75},"(other, Vector) ",[39,1842,1843],{"class":71},"and",[39,1845,290],{"class":119},[39,1847,1627],{"class":75},[39,1849,1850],{"class":71},"==",[39,1852,1853],{"class":75}," other.x ",[39,1855,1843],{"class":71},[39,1857,290],{"class":119},[39,1859,1639],{"class":75},[39,1861,1850],{"class":71},[39,1863,1864],{"class":75}," other.y\n",[39,1866,1867],{"class":41,"line":155},[39,1868,65],{"emptyLinePlaceholder":64},[39,1870,1871,1873,1876,1879],{"class":41,"line":161},[39,1872,260],{"class":71},[39,1874,1875],{"class":119}," __add__",[39,1877,1878],{"class":75},"(self, other):                  ",[39,1880,1881],{"class":45},"# powers +\n",[39,1883,1884,1886,1889,1891,1893,1895,1898,1900,1902,1904],{"class":41,"line":167},[39,1885,810],{"class":71},[39,1887,1888],{"class":75}," Vector(",[39,1890,724],{"class":119},[39,1892,1627],{"class":75},[39,1894,673],{"class":71},[39,1896,1897],{"class":75}," other.x, ",[39,1899,724],{"class":119},[39,1901,1639],{"class":75},[39,1903,673],{"class":71},[39,1905,1906],{"class":75}," other.y)\n",[39,1908,1909],{"class":41,"line":172},[39,1910,65],{"emptyLinePlaceholder":64},[39,1912,1913,1916,1918,1920,1922,1924,1926],{"class":41,"line":184},[39,1914,1915],{"class":75},"v1 ",[39,1917,116],{"class":71},[39,1919,1888],{"class":75},[39,1921,416],{"class":119},[39,1923,123],{"class":75},[39,1925,1664],{"class":119},[39,1927,133],{"class":75},[39,1929,1930,1933,1935,1937,1940,1942,1945],{"class":41,"line":193},[39,1931,1932],{"class":75},"v2 ",[39,1934,116],{"class":71},[39,1936,1888],{"class":75},[39,1938,1939],{"class":119},"3",[39,1941,123],{"class":75},[39,1943,1944],{"class":119},"4",[39,1946,133],{"class":75},[39,1948,1949,1951,1954,1956,1959],{"class":41,"line":201},[39,1950,444],{"class":119},[39,1952,1953],{"class":75},"(v1 ",[39,1955,673],{"class":71},[39,1957,1958],{"class":75}," v2)          ",[39,1960,1961],{"class":45},"# Vector(4, 6) — uses __add__ and __repr__\n",[39,1963,1964,1966,1968,1970,1972,1974,1976,1978,1981],{"class":41,"line":223},[39,1965,444],{"class":119},[39,1967,1953],{"class":75},[39,1969,1850],{"class":71},[39,1971,1888],{"class":75},[39,1973,416],{"class":119},[39,1975,123],{"class":75},[39,1977,1664],{"class":119},[39,1979,1980],{"class":75},"))   ",[39,1982,1983],{"class":45},"# True — uses __eq__\n",[39,1985,1986,1988,1991],{"class":41,"line":252},[39,1987,444],{"class":119},[39,1989,1990],{"class":75},"([v1, v2])                ",[39,1992,1993],{"class":45},"# [Vector(1, 2), Vector(3, 4)] — repr used inside containers\n",[727,1995,1996,1997,2000,2001,2004,2005,123,2008,2010,2011,2014,2015,2018,2019,2022],{},"Without ",[19,1998,1999],{},"__repr__",", printing an instance falls back to the unhelpful default ",[19,2002,2003],{},"\u003C__main__.Vector object at 0x7f...>","; without ",[19,2006,2007],{},"__eq__",[19,2009,1850],{}," falls back to identity comparison (",[19,2012,2013],{},"is","), so two \"equal\" vectors with different ",[19,2016,2017],{},"x","\u002F",[19,2020,2021],{},"y"," values but the same memory address would never occur, but two vectors with identical field values would incorrectly compare unequal.",[14,2024,2026,2027,2030,2031],{"id":2025},"checking-types-isinstance-vs-type","Checking Types: ",[19,2028,2029],{},"isinstance"," vs ",[19,2032,2033],{},"type() ==",[27,2035,2036],{"language":29},[31,2037,2039],{"className":33,"code":2038,"language":29,"meta":35,"style":35},"class Animal:\n    pass\n\nclass Dog(Animal):\n    pass\n\nd = Dog()\n\nprint(isinstance(d, Dog))       # True\nprint(isinstance(d, Animal))      # True — isinstance respects inheritance\nprint(type(d) == Animal)            # False — exact type check, ignores inheritance\nprint(type(d) == Dog)                 # True\n",[19,2040,2041,2050,2054,2058,2072,2076,2080,2090,2094,2107,2121,2140],{"__ignoreMap":35},[39,2042,2043,2045,2048],{"class":41,"line":42},[39,2044,139],{"class":71},[39,2046,2047],{"class":106}," Animal",[39,2049,145],{"class":75},[39,2051,2052],{"class":41,"line":49},[39,2053,1559],{"class":71},[39,2055,2056],{"class":41,"line":55},[39,2057,65],{"emptyLinePlaceholder":64},[39,2059,2060,2062,2065,2067,2070],{"class":41,"line":61},[39,2061,139],{"class":71},[39,2063,2064],{"class":106}," Dog",[39,2066,109],{"class":75},[39,2068,2069],{"class":106},"Animal",[39,2071,1554],{"class":75},[39,2073,2074],{"class":41,"line":68},[39,2075,1559],{"class":71},[39,2077,2078],{"class":41,"line":85},[39,2079,65],{"emptyLinePlaceholder":64},[39,2081,2082,2085,2087],{"class":41,"line":98},[39,2083,2084],{"class":75},"d ",[39,2086,116],{"class":71},[39,2088,2089],{"class":75}," Dog()\n",[39,2091,2092],{"class":41,"line":103},[39,2093,65],{"emptyLinePlaceholder":64},[39,2095,2096,2098,2100,2102,2105],{"class":41,"line":136},[39,2097,444],{"class":119},[39,2099,109],{"class":75},[39,2101,2029],{"class":119},[39,2103,2104],{"class":75},"(d, Dog))       ",[39,2106,1489],{"class":45},[39,2108,2109,2111,2113,2115,2118],{"class":41,"line":148},[39,2110,444],{"class":119},[39,2112,109],{"class":75},[39,2114,2029],{"class":119},[39,2116,2117],{"class":75},"(d, Animal))      ",[39,2119,2120],{"class":45},"# True — isinstance respects inheritance\n",[39,2122,2123,2125,2127,2129,2132,2134,2137],{"class":41,"line":155},[39,2124,444],{"class":119},[39,2126,109],{"class":75},[39,2128,1582],{"class":119},[39,2130,2131],{"class":75},"(d) ",[39,2133,1850],{"class":71},[39,2135,2136],{"class":75}," Animal)            ",[39,2138,2139],{"class":45},"# False — exact type check, ignores inheritance\n",[39,2141,2142,2144,2146,2148,2150,2152,2155],{"class":41,"line":161},[39,2143,444],{"class":119},[39,2145,109],{"class":75},[39,2147,1582],{"class":119},[39,2149,2131],{"class":75},[39,2151,1850],{"class":71},[39,2153,2154],{"class":75}," Dog)                 ",[39,2156,1489],{"class":45},[727,2158,2159,2162,2163,2166,2167,2169,2170,2173],{},[1511,2160,2161],{},"Best practice",": use ",[19,2164,2165],{},"isinstance()",", not ",[19,2168,2033],{},", for type checks in almost all application code — it respects polymorphism and works correctly with subclasses, which is the entire point of inheritance (chapter 13). Reach for exact ",[19,2171,2172],{},"type()"," comparison only in rare cases (like preventing a subclass from being treated identically to its parent in a serializer).",[14,2175,2177],{"id":2176},"class-bodies-execute-immediately-once-at-definition-time","Class Bodies Execute Immediately, Once, at Definition Time",[27,2179,2180],{"language":29},[31,2181,2183],{"className":33,"code":2182,"language":29,"meta":35,"style":35},"print(\"before class\")\n\nclass Demo:\n    print(\"inside class body\")     # runs ONCE, when the class statement executes\n    x = 1 + 1                        # a normal assignment — becomes a class attribute\n    print(f\"x is now {x}\")\n\nprint(\"after class\")\nprint(Demo.x)\n\n# Output:\n# before class\n# inside class body\n# x is now 2\n# after class\n# 2\n",[19,2184,2185,2196,2200,2209,2224,2242,2263,2267,2278,2285,2289,2294,2299,2304,2309,2314],{"__ignoreMap":35},[39,2186,2187,2189,2191,2194],{"class":41,"line":42},[39,2188,444],{"class":119},[39,2190,109],{"class":75},[39,2192,2193],{"class":151},"\"before class\"",[39,2195,133],{"class":75},[39,2197,2198],{"class":41,"line":49},[39,2199,65],{"emptyLinePlaceholder":64},[39,2201,2202,2204,2207],{"class":41,"line":55},[39,2203,139],{"class":71},[39,2205,2206],{"class":106}," Demo",[39,2208,145],{"class":75},[39,2210,2211,2214,2216,2219,2221],{"class":41,"line":61},[39,2212,2213],{"class":119},"    print",[39,2215,109],{"class":75},[39,2217,2218],{"class":151},"\"inside class body\"",[39,2220,246],{"class":75},[39,2222,2223],{"class":45},"# runs ONCE, when the class statement executes\n",[39,2225,2226,2229,2231,2234,2237,2239],{"class":41,"line":68},[39,2227,2228],{"class":75},"    x ",[39,2230,116],{"class":71},[39,2232,2233],{"class":119}," 1",[39,2235,2236],{"class":71}," +",[39,2238,2233],{"class":119},[39,2240,2241],{"class":45},"                        # a normal assignment — becomes a class attribute\n",[39,2243,2244,2246,2248,2250,2253,2255,2257,2259,2261],{"class":41,"line":85},[39,2245,2213],{"class":119},[39,2247,109],{"class":75},[39,2249,323],{"class":71},[39,2251,2252],{"class":151},"\"x is now ",[39,2254,667],{"class":119},[39,2256,2017],{"class":75},[39,2258,338],{"class":119},[39,2260,341],{"class":151},[39,2262,133],{"class":75},[39,2264,2265],{"class":41,"line":98},[39,2266,65],{"emptyLinePlaceholder":64},[39,2268,2269,2271,2273,2276],{"class":41,"line":103},[39,2270,444],{"class":119},[39,2272,109],{"class":75},[39,2274,2275],{"class":151},"\"after class\"",[39,2277,133],{"class":75},[39,2279,2280,2282],{"class":41,"line":136},[39,2281,444],{"class":119},[39,2283,2284],{"class":75},"(Demo.x)\n",[39,2286,2287],{"class":41,"line":148},[39,2288,65],{"emptyLinePlaceholder":64},[39,2290,2291],{"class":41,"line":155},[39,2292,2293],{"class":45},"# Output:\n",[39,2295,2296],{"class":41,"line":161},[39,2297,2298],{"class":45},"# before class\n",[39,2300,2301],{"class":41,"line":167},[39,2302,2303],{"class":45},"# inside class body\n",[39,2305,2306],{"class":41,"line":172},[39,2307,2308],{"class":45},"# x is now 2\n",[39,2310,2311],{"class":41,"line":184},[39,2312,2313],{"class":45},"# after class\n",[39,2315,2316],{"class":41,"line":193},[39,2317,2318],{"class":45},"# 2\n",[727,2320,2321,2322,2324,2325,2328],{},"This surprises newcomers: a ",[19,2323,139],{}," block isn't a template that's \"filled in\" per instance — it's regular Python code that executes top-to-bottom exactly once, building a namespace that becomes the class object. This is precisely why a mutable default like ",[19,2326,2327],{},"items = []"," inside the class body is a single, shared object, not a per-instance template — it's evaluated once, just like a default argument.",[14,2330,2332],{"id":2331},"tips-tricks","💡 Tips & Tricks",[2334,2335,2336,2359,2372,2388,2400],"ul",{},[2337,2338,2339,2162,2342,2344,2345,123,2348,2351,2352,2355,2356,2358],"li",{},[1511,2340,2341],{},"Idiom",[19,2343,1508],{}," for alternate constructors (",[19,2346,2347],{},"from_dict",[19,2349,2350],{},"from_file",") instead of overloading ",[19,2353,2354],{},"__init__"," with optional-argument juggling — ",[19,2357,1527],{}," also makes the constructor subclass-correct automatically.",[2337,2360,2361,178,2364,2367,2368,2371],{},[1511,2362,2363],{},"Debug",[19,2365,2366],{},"vars(obj)"," is equivalent to ",[19,2369,2370],{},"obj.__dict__"," and is the fastest way to inspect exactly what an instance holds, without any class-level attributes mixed in.",[2337,2373,2374,178,2376,2379,2380,123,2382,2384,2385,2387],{},[1511,2375,2341],{},[19,2377,2378],{},"dataclasses.dataclass"," (standard library, 3.7+) auto-generates ",[19,2381,2354],{},[19,2383,1999],{},", and ",[19,2386,2007],{}," for simple data-holding classes — reach for it before hand-writing boilerplate for a class that's mostly fields.",[2337,2389,2390,2393,2394,2396,2397,2399],{},[1511,2391,2392],{},"Performance",": attribute lookup on an instance without ",[19,2395,25],{}," goes through a dict, which is fast but not free — for classes instantiated millions of times (rows, particles, graph nodes), ",[19,2398,25],{}," (chapter 15) meaningfully reduces both memory and lookup overhead.",[2337,2401,2402,178,2404,2407,2408,2411,2412,2415],{},[1511,2403,2363],{},[19,2405,2406],{},"obj.__class__"," and ",[19,2409,2410],{},"type(obj)"," are almost always equivalent, but ",[19,2413,2414],{},"__class__"," can be reassigned at runtime to change an object's class in place — a rarely-used but real technique for state-machine-like objects that \"become\" a different type.",[14,2417,2419],{"id":2418},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2334,2421,2422,2435,2445,2463,2477],{},[2337,2423,2424,2427,2428,2431,2432,2434],{},[1511,2425,2426],{},"A mutable class attribute (list, dict, set) is shared by every instance, since it's created once at class-definition time, not per instance"," — mutating it through one instance (",[19,2429,2430],{},"self.items.append(...)",") affects all instances; always initialize mutable state inside ",[19,2433,2354],{}," as an instance attribute instead.",[2337,2436,2437,2444],{},[1511,2438,2439,2440,2443],{},"Assigning to ",[19,2441,2442],{},"self.attr = value"," never mutates a same-named class attribute — it always creates or overwrites an instance attribute that shadows it"," — this can mask bugs where code intends to update shared state but silently creates per-instance state instead.",[2337,2446,2447,2462],{},[1511,2448,2449,2450,2453,2454,2457,2458,2461],{},"Python objects have no true privacy — a leading underscore (",[19,2451,2452],{},"_x",") is convention only, and even \"name-mangled\" double-underscore attributes (",[19,2455,2456],{},"__x",") are just renamed to ",[19,2459,2460],{},"_ClassName__x",", still fully accessible"," — don't rely on attribute naming for security or correctness; it communicates intent to other developers, nothing more.",[2337,2464,2465,2468,2469,2472,2473,2476],{},[1511,2466,2467],{},"The class body runs exactly once at definition time — code inside it (loops, conditionals, print statements, function calls) executes immediately, not per instantiation"," — this is why conditional class attributes (",[19,2470,2471],{},"x = compute_default() if condition else other_default()",") are evaluated once at import time, not refreshed on each ",[19,2474,2475],{},"ClassName()"," call.",[2337,2478,2479,2495,2496,2499,2500,2503,2504,2018,2506,2509,2510,2018,2512,742],{},[1511,2480,2481,2484,2485,2487,2488,2491,2492],{},[19,2482,2483],{},"isinstance(True, int)"," is ",[19,2486,120],{},", because ",[19,2489,2490],{},"bool"," is a subclass of ",[19,2493,2494],{},"int"," — a type check meant to exclude booleans (e.g., validating \"this must be an integer count, not a flag\") must explicitly guard with ",[19,2497,2498],{},"type(x) is bool"," in addition to the ",[19,2501,2502],{},"isinstance(x, int)"," check, or a stray ",[19,2505,120],{},[19,2507,2508],{},"False"," will silently pass validation as ",[19,2511,416],{},[19,2513,2514],{},"0",[14,2516,2518],{"id":2517},"spot-the-bug","🧠 Spot the Bug",[727,2520,2521],{},"A game inventory system tracks items per player. Every new player mysteriously starts with items already in their inventory. Find the bug.",[27,2523,2524],{"language":29},[31,2525,2527],{"className":33,"code":2526,"language":29,"meta":35,"style":35},"class Player:\n    inventory = []\n\n    def __init__(self, name):\n        self.name = name\n\n    def pick_up(self, item):\n        self.inventory.append(item)\n\nalice = Player(\"Alice\")\nalice.pick_up(\"sword\")\n\nbob = Player(\"Bob\")\nprint(bob.inventory)\n",[19,2528,2529,2538,2548,2552,2560,2571,2575,2584,2591,2595,2608,2618,2622,2634],{"__ignoreMap":35},[39,2530,2531,2533,2536],{"class":41,"line":42},[39,2532,139],{"class":71},[39,2534,2535],{"class":106}," Player",[39,2537,145],{"class":75},[39,2539,2540,2543,2545],{"class":41,"line":49},[39,2541,2542],{"class":75},"    inventory ",[39,2544,116],{"class":71},[39,2546,2547],{"class":75}," []\n",[39,2549,2550],{"class":41,"line":55},[39,2551,65],{"emptyLinePlaceholder":64},[39,2553,2554,2556,2558],{"class":41,"line":61},[39,2555,260],{"class":71},[39,2557,765],{"class":119},[39,2559,909],{"class":75},[39,2561,2562,2564,2566,2568],{"class":41,"line":68},[39,2563,772],{"class":119},[39,2565,916],{"class":75},[39,2567,116],{"class":71},[39,2569,2570],{"class":75}," name\n",[39,2572,2573],{"class":41,"line":85},[39,2574,65],{"emptyLinePlaceholder":64},[39,2576,2577,2579,2582],{"class":41,"line":98},[39,2578,260],{"class":71},[39,2580,2581],{"class":106}," pick_up",[39,2583,1117],{"class":75},[39,2585,2586,2588],{"class":41,"line":103},[39,2587,772],{"class":119},[39,2589,2590],{"class":75},".inventory.append(item)\n",[39,2592,2593],{"class":41,"line":136},[39,2594,65],{"emptyLinePlaceholder":64},[39,2596,2597,2599,2601,2604,2606],{"class":41,"line":148},[39,2598,933],{"class":75},[39,2600,116],{"class":71},[39,2602,2603],{"class":75}," Player(",[39,2605,941],{"class":151},[39,2607,133],{"class":75},[39,2609,2610,2613,2616],{"class":41,"line":155},[39,2611,2612],{"class":75},"alice.pick_up(",[39,2614,2615],{"class":151},"\"sword\"",[39,2617,133],{"class":75},[39,2619,2620],{"class":41,"line":161},[39,2621,65],{"emptyLinePlaceholder":64},[39,2623,2624,2626,2628,2630,2632],{"class":41,"line":167},[39,2625,948],{"class":75},[39,2627,116],{"class":71},[39,2629,2603],{"class":75},[39,2631,955],{"class":151},[39,2633,133],{"class":75},[39,2635,2636,2638],{"class":41,"line":172},[39,2637,444],{"class":119},[39,2639,2640],{"class":75},"(bob.inventory)\n",[2642,2643,2644,2648,2681,2690,2752],"details",{},[2645,2646,2647],"summary",{},"Answer",[727,2649,2650,2651,2654,2655,2658,2659,2662,2663,2666,2667,2670,2671,2673,2674,2677,2678,2680],{},"Prints ",[19,2652,2653],{},"['sword']"," — Bob starts with Alice's sword. ",[19,2656,2657],{},"inventory = []"," is a ",[1511,2660,2661],{},"class attribute",", created exactly once when the ",[19,2664,2665],{},"Player"," class body executes, and shared by every instance that hasn't shadowed it with its own instance attribute. ",[19,2668,2669],{},"self.inventory.append(item)"," doesn't create a new list on ",[19,2672,724],{}," — it looks up ",[19,2675,2676],{},"inventory"," (finds the shared class attribute, since no instance attribute exists yet), and mutates that single shared list in place. Every ",[19,2679,2665],{}," instance is looking at the exact same list object.",[727,2682,2683,2684,2686,2687,2689],{},"The fix is to make ",[19,2685,2676],{}," an instance attribute, created fresh in ",[19,2688,2354],{},":",[27,2691,2692],{"language":29},[31,2693,2695],{"className":33,"code":2694,"language":29,"meta":35,"style":35},"class Player:\n    def __init__(self, name):\n        self.name = name\n        self.inventory = []\n\n    def pick_up(self, item):\n        self.inventory.append(item)\n",[19,2696,2697,2705,2713,2723,2734,2738,2746],{"__ignoreMap":35},[39,2698,2699,2701,2703],{"class":41,"line":42},[39,2700,139],{"class":71},[39,2702,2535],{"class":106},[39,2704,145],{"class":75},[39,2706,2707,2709,2711],{"class":41,"line":49},[39,2708,260],{"class":71},[39,2710,765],{"class":119},[39,2712,909],{"class":75},[39,2714,2715,2717,2719,2721],{"class":41,"line":55},[39,2716,772],{"class":119},[39,2718,916],{"class":75},[39,2720,116],{"class":71},[39,2722,2570],{"class":75},[39,2724,2725,2727,2730,2732],{"class":41,"line":61},[39,2726,772],{"class":119},[39,2728,2729],{"class":75},".inventory ",[39,2731,116],{"class":71},[39,2733,2547],{"class":75},[39,2735,2736],{"class":41,"line":68},[39,2737,65],{"emptyLinePlaceholder":64},[39,2739,2740,2742,2744],{"class":41,"line":85},[39,2741,260],{"class":71},[39,2743,2581],{"class":106},[39,2745,1117],{"class":75},[39,2747,2748,2750],{"class":41,"line":98},[39,2749,772],{"class":119},[39,2751,2590],{"class":75},[727,2753,2754,2757,2758,2760,2761,2764],{},[1511,2755,2756],{},"The lesson",": mutable class attributes are shared, in-place-mutable state across every instance — any field meant to be per-instance and mutable (lists, dicts, sets, and most custom objects) belongs in ",[19,2759,2354],{}," as ",[19,2762,2763],{},"self.attr = ...",", never as a bare class-body assignment.",[14,2766,2768],{"id":2767},"key-takeaways","Key Takeaways",[2334,2770,2771,2783,2790,2796,2815,2830],{},[2337,2772,2773,2775,2776,2778,2779,2782],{},[19,2774,2354],{}," initializes an already-created instance; ",[19,2777,724],{}," is just the conventional name for the instance, passed automatically by the ",[19,2780,2781],{},"obj.method()"," calling convention.",[2337,2784,2785,2786,2789],{},"Class attributes are shared across all instances and created once at class-definition time; instance attributes (assigned via ",[19,2787,2788],{},"self.x = ...",") are per-object and shadow same-named class attributes on lookup.",[2337,2791,2792,2793,2795],{},"Mutable class attributes are a classic bug source — the fix is always to initialize mutable state inside ",[19,2794,2354],{}," instead of the class body.",[2337,2797,2798,2800,2801,2804,2805,2808,2809,2811,2812,2814],{},[19,2799,1508],{}," (receives ",[19,2802,2803],{},"cls",") is the idiomatic pattern for alternate constructors; ",[19,2806,2807],{},"@staticmethod"," (receives neither ",[19,2810,724],{}," nor ",[19,2813,2803],{},") is just a regularly-namespaced function living on the class.",[2337,2816,2817,2818,2820,2821,2823,2824,2826,2827,2829],{},"Prefer ",[19,2819,2165],{}," over ",[19,2822,2033],{}," for type checks so subclasses are correctly recognized — but remember ",[19,2825,2490],{}," is a subtype of ",[19,2828,2494],{},", which can silently defeat naive numeric validation.",[2337,2831,2832],{},"A class body executes top-to-bottom exactly once at definition time, building the class's namespace — it is not a per-instance template.",[2834,2835,2836],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":35,"searchDepth":49,"depth":49,"links":2838},[2839,2841,2843,2846,2847,2849,2850,2852,2853,2854,2855,2856],{"id":16,"depth":49,"text":2840},"Production Data Classes — @dataclass with Validation and __slots__",{"id":721,"depth":49,"text":2842},"self Is Not Magic",{"id":866,"depth":49,"text":867,"children":2844},[2845],{"id":1068,"depth":55,"text":1069},{"id":1273,"depth":49,"text":1274},{"id":1591,"depth":49,"text":2848},"Every Instance Has a __dict__ (Usually)",{"id":1720,"depth":49,"text":1721},{"id":2025,"depth":49,"text":2851},"Checking Types: isinstance vs type() ==",{"id":2176,"depth":49,"text":2177},{"id":2331,"depth":49,"text":2332},{"id":2418,"depth":49,"text":2419},{"id":2517,"depth":49,"text":2518},{"id":2767,"depth":49,"text":2768},"md",{},"\u002Fpython\u002F12-classes-and-objects",{"title":5,"description":35},"python\u002F12-classes-and-objects","1y738O0iUzDPFdq-hDcubWeU_yCRfwz0lWxY5ULyyqo",1789924651507]