[{"data":1,"prerenderedAt":4374},["ShallowReactive",2],{"page-\u002Fpython\u002F14-magic-methods-and-protocols":3},{"id":4,"title":5,"body":6,"description":27,"extension":4368,"meta":4369,"navigation":63,"path":4370,"seo":4371,"stem":4372,"__hash__":4373},"content\u002Fpython\u002F14-magic-methods-and-protocols.md","14 — Magic Methods & Protocols",{"type":7,"value":8,"toc":4346},"minimark",[9,13,18,1288,1299,1518,1539,1699,1713,1718,1906,1923,1936,2192,2225,2231,2403,2422,2426,2811,2852,2862,3038,3052,3218,3231,3235,3426,3442,3449,3579,3585,3589,3706,3710,3838,3842,3849,4058,4268,4272,4342],[10,11,5],"h1",{"id":12},"_14-magic-methods-protocols",[14,15,17],"h2",{"id":16},"production-grade-numeric-class-full-operator-protocol","Production-Grade Numeric Class — Full Operator Protocol",[19,20,22],"code-wrapper",{"language":21},"python",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","# ── A production Vector class implementing the full numeric protocol ──\n# Demonstrates __repr__, __eq__, __hash__, __add__, __radd__, __mul__,\n# __rmul__, __neg__, __abs__, __bool__, and iteration — the complete set\n# needed for a value type to behave like a first-class numeric citizen.\n\nimport math\nfrom functools import total_ordering\n\nclass Vector:\n    \"\"\"2D vector with full operator support, hashing, and iteration.\"\"\"\n\n    __slots__ = (\"_x\", \"_y\")   # no __dict__ — saves ~40% memory per instance\n\n    def __init__(self, x: float, y: float):\n        self._x = float(x)\n        self._y = float(y)\n\n    # ── Representation: __repr__ for debugging, __str__ for display ──\n    def __repr__(self) -> str:\n        # Should be unambiguous — ideally eval-able: Vector(repr(v)) == v\n        return f\"Vector({self._x!r}, {self._y!r})\"\n\n    def __str__(self) -> str:\n        return f\"({self._x:.2f}, {self._y:.2f})\"\n\n    # ── Property access: read-only via slots, no setter needed ──\n    @property\n    def x(self) -> float:\n        return self._x\n\n    @property\n    def y(self) -> float:\n        return self._y\n\n    # ── Equality and hashing: MUST be consistent (a == b ⟹ hash(a) == hash(b)) ──\n    def __eq__(self, other: object) -> bool:\n        if not isinstance(other, Vector):\n            return NotImplemented   # NOT False — lets Python try other.__eq__(self)\n        return self._x == other._x and self._y == other._y\n\n    def __hash__(self) -> int:\n        # Hash MUST use the same fields as __eq__ — if x\u002Fy change, hash must change\n        # Since __slots__ + no setter = immutable, this is safe\n        return hash((self._x, self._y))\n\n    # ── Arithmetic: __add__ + __radd__ for commutative addition ──\n    def __add__(self, other: \"Vector\") -> \"Vector\":\n        if not isinstance(other, Vector):\n            return NotImplemented   # lets Python try other.__radd__(self)\n        return Vector(self._x + other._x, self._y + other._y)\n\n    def __radd__(self, other: \"Vector\") -> \"Vector\":\n        # Called when `other + self` fails (other doesn't know about Vector)\n        # For commutative ops, just delegate to __add__\n        return self.__add__(other)\n\n    # ── Scalar multiplication: __mul__ (vec * scalar) + __rmul__ (scalar * vec) ──\n    def __mul__(self, scalar: float) -> \"Vector\":\n        if not isinstance(scalar, (int, float)):\n            return NotImplemented\n        return Vector(self._x * scalar, self._y * scalar)\n\n    def __rmul__(self, scalar: float) -> \"Vector\":\n        return self.__mul__(scalar)   # scalar * vec == vec * scalar\n\n    def __neg__(self) -> \"Vector\":\n        return Vector(-self._x, -self._y)\n\n    # ── Built-in function integration ──\n    def __abs__(self) -> float:\n        return math.hypot(self._x, self._y)   # math.hypot avoids overflow for large values\n\n    def __bool__(self) -> bool:\n        return bool(self._x) or bool(self._y)  # zero vector is falsy\n\n    def __iter__(self):\n        # Enables unpacking: x, y = vector\n        yield self._x\n        yield self._y\n\n    def __len__(self) -> int:\n        return 2   # dimensionality — enables len(vec)\n\n# ── Usage: the Vector now behaves like a built-in numeric type ──\nv1 = Vector(3, 4)\nv2 = Vector(1, 2)\n\nprint(v1 + v2)         # Vector(4.0, 6.0) — __add__\nprint(v1 * 2)          # Vector(6.0, 8.0) — __mul__\nprint(2 * v1)          # Vector(6.0, 8.0) — __rmul__ (int.__mul__ fails, falls back)\nprint(-v1)             # Vector(-3.0, -4.0) — __neg__\nprint(abs(v1))         # 5.0 — __abs__ via math.hypot\nprint(bool(Vector(0, 0)))  # False — zero vector is falsy\nprint(v1 == Vector(3, 4))  # True — __eq__\n\n# Hashable → usable as dict keys and set members\npoints = {v1: \"origin-ref\", v2: \"other\"}\nprint(points[Vector(3, 4)])  # \"origin-ref\" — found via __hash__ + __eq__\n\n# Iterable → unpacking works\nx, y = v1\nprint(x, y)   # 3.0 4.0\n","",[29,30,31,40,46,52,58,65,76,90,95,108,115,120,148,153,176,194,209,214,220,236,242,280,285,299,330,335,341,350,364,375,380,387,401,411,416,422,444,459,471,498,503,518,524,530,552,557,563,582,593,603,630,635,653,659,665,681,686,692,711,732,740,766,771,789,807,812,826,847,852,858,872,892,897,911,942,947,958,964,974,983,988,1002,1013,1018,1024,1045,1065,1070,1087,1104,1122,1137,1153,1178,1201,1206,1212,1235,1255,1260,1266,1277],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── A production Vector class implementing the full numeric protocol ──\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"# Demonstrates __repr__, __eq__, __hash__, __add__, __radd__, __mul__,\n",[32,47,49],{"class":34,"line":48},3,[32,50,51],{"class":38},"# __rmul__, __neg__, __abs__, __bool__, and iteration — the complete set\n",[32,53,55],{"class":34,"line":54},4,[32,56,57],{"class":38},"# needed for a value type to behave like a first-class numeric citizen.\n",[32,59,61],{"class":34,"line":60},5,[32,62,64],{"emptyLinePlaceholder":63},true,"\n",[32,66,68,72],{"class":34,"line":67},6,[32,69,71],{"class":70},"svdQ7","import",[32,73,75],{"class":74},"ssxIu"," math\n",[32,77,79,82,85,87],{"class":34,"line":78},7,[32,80,81],{"class":70},"from",[32,83,84],{"class":74}," functools ",[32,86,71],{"class":70},[32,88,89],{"class":74}," total_ordering\n",[32,91,93],{"class":34,"line":92},8,[32,94,64],{"emptyLinePlaceholder":63},[32,96,98,101,105],{"class":34,"line":97},9,[32,99,100],{"class":70},"class",[32,102,104],{"class":103},"sIsaT"," Vector",[32,106,107],{"class":74},":\n",[32,109,111],{"class":34,"line":110},10,[32,112,114],{"class":113},"sJ6F3","    \"\"\"2D vector with full operator support, hashing, and iteration.\"\"\"\n",[32,116,118],{"class":34,"line":117},11,[32,119,64],{"emptyLinePlaceholder":63},[32,121,123,127,130,133,136,139,142,145],{"class":34,"line":122},12,[32,124,126],{"class":125},"snvgF","    __slots__",[32,128,129],{"class":70}," =",[32,131,132],{"class":74}," (",[32,134,135],{"class":113},"\"_x\"",[32,137,138],{"class":74},", ",[32,140,141],{"class":113},"\"_y\"",[32,143,144],{"class":74},")   ",[32,146,147],{"class":38},"# no __dict__ — saves ~40% memory per instance\n",[32,149,151],{"class":34,"line":150},13,[32,152,64],{"emptyLinePlaceholder":63},[32,154,156,159,162,165,168,171,173],{"class":34,"line":155},14,[32,157,158],{"class":70},"    def",[32,160,161],{"class":125}," __init__",[32,163,164],{"class":74},"(self, x: ",[32,166,167],{"class":125},"float",[32,169,170],{"class":74},", y: ",[32,172,167],{"class":125},[32,174,175],{"class":74},"):\n",[32,177,179,182,185,188,191],{"class":34,"line":178},15,[32,180,181],{"class":125},"        self",[32,183,184],{"class":74},"._x ",[32,186,187],{"class":70},"=",[32,189,190],{"class":125}," float",[32,192,193],{"class":74},"(x)\n",[32,195,197,199,202,204,206],{"class":34,"line":196},16,[32,198,181],{"class":125},[32,200,201],{"class":74},"._y ",[32,203,187],{"class":70},[32,205,190],{"class":125},[32,207,208],{"class":74},"(y)\n",[32,210,212],{"class":34,"line":211},17,[32,213,64],{"emptyLinePlaceholder":63},[32,215,217],{"class":34,"line":216},18,[32,218,219],{"class":38},"    # ── Representation: __repr__ for debugging, __str__ for display ──\n",[32,221,223,225,228,231,234],{"class":34,"line":222},19,[32,224,158],{"class":70},[32,226,227],{"class":125}," __repr__",[32,229,230],{"class":74},"(self) -> ",[32,232,233],{"class":125},"str",[32,235,107],{"class":74},[32,237,239],{"class":34,"line":238},20,[32,240,241],{"class":38},"        # Should be unambiguous — ideally eval-able: Vector(repr(v)) == v\n",[32,243,245,248,251,254,257,260,263,266,268,270,273,275,277],{"class":34,"line":244},21,[32,246,247],{"class":70},"        return",[32,249,250],{"class":70}," f",[32,252,253],{"class":113},"\"Vector(",[32,255,256],{"class":125},"{self",[32,258,259],{"class":74},"._x",[32,261,262],{"class":70},"!r",[32,264,265],{"class":125},"}",[32,267,138],{"class":113},[32,269,256],{"class":125},[32,271,272],{"class":74},"._y",[32,274,262],{"class":70},[32,276,265],{"class":125},[32,278,279],{"class":113},")\"\n",[32,281,283],{"class":34,"line":282},22,[32,284,64],{"emptyLinePlaceholder":63},[32,286,288,290,293,295,297],{"class":34,"line":287},23,[32,289,158],{"class":70},[32,291,292],{"class":125}," __str__",[32,294,230],{"class":74},[32,296,233],{"class":125},[32,298,107],{"class":74},[32,300,302,304,306,309,311,313,316,318,320,322,324,326,328],{"class":34,"line":301},24,[32,303,247],{"class":70},[32,305,250],{"class":70},[32,307,308],{"class":113},"\"(",[32,310,256],{"class":125},[32,312,259],{"class":74},[32,314,315],{"class":70},":.2f",[32,317,265],{"class":125},[32,319,138],{"class":113},[32,321,256],{"class":125},[32,323,272],{"class":74},[32,325,315],{"class":70},[32,327,265],{"class":125},[32,329,279],{"class":113},[32,331,333],{"class":34,"line":332},25,[32,334,64],{"emptyLinePlaceholder":63},[32,336,338],{"class":34,"line":337},26,[32,339,340],{"class":38},"    # ── Property access: read-only via slots, no setter needed ──\n",[32,342,344,347],{"class":34,"line":343},27,[32,345,346],{"class":103},"    @",[32,348,349],{"class":125},"property\n",[32,351,353,355,358,360,362],{"class":34,"line":352},28,[32,354,158],{"class":70},[32,356,357],{"class":103}," x",[32,359,230],{"class":74},[32,361,167],{"class":125},[32,363,107],{"class":74},[32,365,367,369,372],{"class":34,"line":366},29,[32,368,247],{"class":70},[32,370,371],{"class":125}," self",[32,373,374],{"class":74},"._x\n",[32,376,378],{"class":34,"line":377},30,[32,379,64],{"emptyLinePlaceholder":63},[32,381,383,385],{"class":34,"line":382},31,[32,384,346],{"class":103},[32,386,349],{"class":125},[32,388,390,392,395,397,399],{"class":34,"line":389},32,[32,391,158],{"class":70},[32,393,394],{"class":103}," y",[32,396,230],{"class":74},[32,398,167],{"class":125},[32,400,107],{"class":74},[32,402,404,406,408],{"class":34,"line":403},33,[32,405,247],{"class":70},[32,407,371],{"class":125},[32,409,410],{"class":74},"._y\n",[32,412,414],{"class":34,"line":413},34,[32,415,64],{"emptyLinePlaceholder":63},[32,417,419],{"class":34,"line":418},35,[32,420,421],{"class":38},"    # ── Equality and hashing: MUST be consistent (a == b ⟹ hash(a) == hash(b)) ──\n",[32,423,425,427,430,433,436,439,442],{"class":34,"line":424},36,[32,426,158],{"class":70},[32,428,429],{"class":125}," __eq__",[32,431,432],{"class":74},"(self, other: ",[32,434,435],{"class":125},"object",[32,437,438],{"class":74},") -> ",[32,440,441],{"class":125},"bool",[32,443,107],{"class":74},[32,445,447,450,453,456],{"class":34,"line":446},37,[32,448,449],{"class":70},"        if",[32,451,452],{"class":70}," not",[32,454,455],{"class":125}," isinstance",[32,457,458],{"class":74},"(other, Vector):\n",[32,460,462,465,468],{"class":34,"line":461},38,[32,463,464],{"class":70},"            return",[32,466,467],{"class":125}," NotImplemented",[32,469,470],{"class":38},"   # NOT False — lets Python try other.__eq__(self)\n",[32,472,474,476,478,480,483,486,489,491,493,495],{"class":34,"line":473},39,[32,475,247],{"class":70},[32,477,371],{"class":125},[32,479,184],{"class":74},[32,481,482],{"class":70},"==",[32,484,485],{"class":74}," other._x ",[32,487,488],{"class":70},"and",[32,490,371],{"class":125},[32,492,201],{"class":74},[32,494,482],{"class":70},[32,496,497],{"class":74}," other._y\n",[32,499,501],{"class":34,"line":500},40,[32,502,64],{"emptyLinePlaceholder":63},[32,504,506,508,511,513,516],{"class":34,"line":505},41,[32,507,158],{"class":70},[32,509,510],{"class":125}," __hash__",[32,512,230],{"class":74},[32,514,515],{"class":125},"int",[32,517,107],{"class":74},[32,519,521],{"class":34,"line":520},42,[32,522,523],{"class":38},"        # Hash MUST use the same fields as __eq__ — if x\u002Fy change, hash must change\n",[32,525,527],{"class":34,"line":526},43,[32,528,529],{"class":38},"        # Since __slots__ + no setter = immutable, this is safe\n",[32,531,533,535,538,541,544,547,549],{"class":34,"line":532},44,[32,534,247],{"class":70},[32,536,537],{"class":125}," hash",[32,539,540],{"class":74},"((",[32,542,543],{"class":125},"self",[32,545,546],{"class":74},"._x, ",[32,548,543],{"class":125},[32,550,551],{"class":74},"._y))\n",[32,553,555],{"class":34,"line":554},45,[32,556,64],{"emptyLinePlaceholder":63},[32,558,560],{"class":34,"line":559},46,[32,561,562],{"class":38},"    # ── Arithmetic: __add__ + __radd__ for commutative addition ──\n",[32,564,566,568,571,573,576,578,580],{"class":34,"line":565},47,[32,567,158],{"class":70},[32,569,570],{"class":125}," __add__",[32,572,432],{"class":74},[32,574,575],{"class":113},"\"Vector\"",[32,577,438],{"class":74},[32,579,575],{"class":113},[32,581,107],{"class":74},[32,583,585,587,589,591],{"class":34,"line":584},48,[32,586,449],{"class":70},[32,588,452],{"class":70},[32,590,455],{"class":125},[32,592,458],{"class":74},[32,594,596,598,600],{"class":34,"line":595},49,[32,597,464],{"class":70},[32,599,467],{"class":125},[32,601,602],{"class":38},"   # lets Python try other.__radd__(self)\n",[32,604,606,608,611,613,615,618,621,623,625,627],{"class":34,"line":605},50,[32,607,247],{"class":70},[32,609,610],{"class":74}," Vector(",[32,612,543],{"class":125},[32,614,184],{"class":74},[32,616,617],{"class":70},"+",[32,619,620],{"class":74}," other._x, ",[32,622,543],{"class":125},[32,624,201],{"class":74},[32,626,617],{"class":70},[32,628,629],{"class":74}," other._y)\n",[32,631,633],{"class":34,"line":632},51,[32,634,64],{"emptyLinePlaceholder":63},[32,636,638,640,643,645,647,649,651],{"class":34,"line":637},52,[32,639,158],{"class":70},[32,641,642],{"class":125}," __radd__",[32,644,432],{"class":74},[32,646,575],{"class":113},[32,648,438],{"class":74},[32,650,575],{"class":113},[32,652,107],{"class":74},[32,654,656],{"class":34,"line":655},53,[32,657,658],{"class":38},"        # Called when `other + self` fails (other doesn't know about Vector)\n",[32,660,662],{"class":34,"line":661},54,[32,663,664],{"class":38},"        # For commutative ops, just delegate to __add__\n",[32,666,668,670,672,675,678],{"class":34,"line":667},55,[32,669,247],{"class":70},[32,671,371],{"class":125},[32,673,674],{"class":74},".",[32,676,677],{"class":125},"__add__",[32,679,680],{"class":74},"(other)\n",[32,682,684],{"class":34,"line":683},56,[32,685,64],{"emptyLinePlaceholder":63},[32,687,689],{"class":34,"line":688},57,[32,690,691],{"class":38},"    # ── Scalar multiplication: __mul__ (vec * scalar) + __rmul__ (scalar * vec) ──\n",[32,693,695,697,700,703,705,707,709],{"class":34,"line":694},58,[32,696,158],{"class":70},[32,698,699],{"class":125}," __mul__",[32,701,702],{"class":74},"(self, scalar: ",[32,704,167],{"class":125},[32,706,438],{"class":74},[32,708,575],{"class":113},[32,710,107],{"class":74},[32,712,714,716,718,720,723,725,727,729],{"class":34,"line":713},59,[32,715,449],{"class":70},[32,717,452],{"class":70},[32,719,455],{"class":125},[32,721,722],{"class":74},"(scalar, (",[32,724,515],{"class":125},[32,726,138],{"class":74},[32,728,167],{"class":125},[32,730,731],{"class":74},")):\n",[32,733,735,737],{"class":34,"line":734},60,[32,736,464],{"class":70},[32,738,739],{"class":125}," NotImplemented\n",[32,741,743,745,747,749,751,754,757,759,761,763],{"class":34,"line":742},61,[32,744,247],{"class":70},[32,746,610],{"class":74},[32,748,543],{"class":125},[32,750,184],{"class":74},[32,752,753],{"class":70},"*",[32,755,756],{"class":74}," scalar, ",[32,758,543],{"class":125},[32,760,201],{"class":74},[32,762,753],{"class":70},[32,764,765],{"class":74}," scalar)\n",[32,767,769],{"class":34,"line":768},62,[32,770,64],{"emptyLinePlaceholder":63},[32,772,774,776,779,781,783,785,787],{"class":34,"line":773},63,[32,775,158],{"class":70},[32,777,778],{"class":125}," __rmul__",[32,780,702],{"class":74},[32,782,167],{"class":125},[32,784,438],{"class":74},[32,786,575],{"class":113},[32,788,107],{"class":74},[32,790,792,794,796,798,801,804],{"class":34,"line":791},64,[32,793,247],{"class":70},[32,795,371],{"class":125},[32,797,674],{"class":74},[32,799,800],{"class":125},"__mul__",[32,802,803],{"class":74},"(scalar)   ",[32,805,806],{"class":38},"# scalar * vec == vec * scalar\n",[32,808,810],{"class":34,"line":809},65,[32,811,64],{"emptyLinePlaceholder":63},[32,813,815,817,820,822,824],{"class":34,"line":814},66,[32,816,158],{"class":70},[32,818,819],{"class":125}," __neg__",[32,821,230],{"class":74},[32,823,575],{"class":113},[32,825,107],{"class":74},[32,827,829,831,833,836,838,840,842,844],{"class":34,"line":828},67,[32,830,247],{"class":70},[32,832,610],{"class":74},[32,834,835],{"class":70},"-",[32,837,543],{"class":125},[32,839,546],{"class":74},[32,841,835],{"class":70},[32,843,543],{"class":125},[32,845,846],{"class":74},"._y)\n",[32,848,850],{"class":34,"line":849},68,[32,851,64],{"emptyLinePlaceholder":63},[32,853,855],{"class":34,"line":854},69,[32,856,857],{"class":38},"    # ── Built-in function integration ──\n",[32,859,861,863,866,868,870],{"class":34,"line":860},70,[32,862,158],{"class":70},[32,864,865],{"class":125}," __abs__",[32,867,230],{"class":74},[32,869,167],{"class":125},[32,871,107],{"class":74},[32,873,875,877,880,882,884,886,889],{"class":34,"line":874},71,[32,876,247],{"class":70},[32,878,879],{"class":74}," math.hypot(",[32,881,543],{"class":125},[32,883,546],{"class":74},[32,885,543],{"class":125},[32,887,888],{"class":74},"._y)   ",[32,890,891],{"class":38},"# math.hypot avoids overflow for large values\n",[32,893,895],{"class":34,"line":894},72,[32,896,64],{"emptyLinePlaceholder":63},[32,898,900,902,905,907,909],{"class":34,"line":899},73,[32,901,158],{"class":70},[32,903,904],{"class":125}," __bool__",[32,906,230],{"class":74},[32,908,441],{"class":125},[32,910,107],{"class":74},[32,912,914,916,919,922,924,927,930,932,934,936,939],{"class":34,"line":913},74,[32,915,247],{"class":70},[32,917,918],{"class":125}," bool",[32,920,921],{"class":74},"(",[32,923,543],{"class":125},[32,925,926],{"class":74},"._x) ",[32,928,929],{"class":70},"or",[32,931,918],{"class":125},[32,933,921],{"class":74},[32,935,543],{"class":125},[32,937,938],{"class":74},"._y)  ",[32,940,941],{"class":38},"# zero vector is falsy\n",[32,943,945],{"class":34,"line":944},75,[32,946,64],{"emptyLinePlaceholder":63},[32,948,950,952,955],{"class":34,"line":949},76,[32,951,158],{"class":70},[32,953,954],{"class":125}," __iter__",[32,956,957],{"class":74},"(self):\n",[32,959,961],{"class":34,"line":960},77,[32,962,963],{"class":38},"        # Enables unpacking: x, y = vector\n",[32,965,967,970,972],{"class":34,"line":966},78,[32,968,969],{"class":70},"        yield",[32,971,371],{"class":125},[32,973,374],{"class":74},[32,975,977,979,981],{"class":34,"line":976},79,[32,978,969],{"class":70},[32,980,371],{"class":125},[32,982,410],{"class":74},[32,984,986],{"class":34,"line":985},80,[32,987,64],{"emptyLinePlaceholder":63},[32,989,991,993,996,998,1000],{"class":34,"line":990},81,[32,992,158],{"class":70},[32,994,995],{"class":125}," __len__",[32,997,230],{"class":74},[32,999,515],{"class":125},[32,1001,107],{"class":74},[32,1003,1005,1007,1010],{"class":34,"line":1004},82,[32,1006,247],{"class":70},[32,1008,1009],{"class":125}," 2",[32,1011,1012],{"class":38},"   # dimensionality — enables len(vec)\n",[32,1014,1016],{"class":34,"line":1015},83,[32,1017,64],{"emptyLinePlaceholder":63},[32,1019,1021],{"class":34,"line":1020},84,[32,1022,1023],{"class":38},"# ── Usage: the Vector now behaves like a built-in numeric type ──\n",[32,1025,1027,1030,1032,1034,1037,1039,1042],{"class":34,"line":1026},85,[32,1028,1029],{"class":74},"v1 ",[32,1031,187],{"class":70},[32,1033,610],{"class":74},[32,1035,1036],{"class":125},"3",[32,1038,138],{"class":74},[32,1040,1041],{"class":125},"4",[32,1043,1044],{"class":74},")\n",[32,1046,1048,1051,1053,1055,1058,1060,1063],{"class":34,"line":1047},86,[32,1049,1050],{"class":74},"v2 ",[32,1052,187],{"class":70},[32,1054,610],{"class":74},[32,1056,1057],{"class":125},"1",[32,1059,138],{"class":74},[32,1061,1062],{"class":125},"2",[32,1064,1044],{"class":74},[32,1066,1068],{"class":34,"line":1067},87,[32,1069,64],{"emptyLinePlaceholder":63},[32,1071,1073,1076,1079,1081,1084],{"class":34,"line":1072},88,[32,1074,1075],{"class":125},"print",[32,1077,1078],{"class":74},"(v1 ",[32,1080,617],{"class":70},[32,1082,1083],{"class":74}," v2)         ",[32,1085,1086],{"class":38},"# Vector(4.0, 6.0) — __add__\n",[32,1088,1090,1092,1094,1096,1098,1101],{"class":34,"line":1089},89,[32,1091,1075],{"class":125},[32,1093,1078],{"class":74},[32,1095,753],{"class":70},[32,1097,1009],{"class":125},[32,1099,1100],{"class":74},")          ",[32,1102,1103],{"class":38},"# Vector(6.0, 8.0) — __mul__\n",[32,1105,1107,1109,1111,1113,1116,1119],{"class":34,"line":1106},90,[32,1108,1075],{"class":125},[32,1110,921],{"class":74},[32,1112,1062],{"class":125},[32,1114,1115],{"class":70}," *",[32,1117,1118],{"class":74}," v1)          ",[32,1120,1121],{"class":38},"# Vector(6.0, 8.0) — __rmul__ (int.__mul__ fails, falls back)\n",[32,1123,1125,1127,1129,1131,1134],{"class":34,"line":1124},91,[32,1126,1075],{"class":125},[32,1128,921],{"class":74},[32,1130,835],{"class":70},[32,1132,1133],{"class":74},"v1)             ",[32,1135,1136],{"class":38},"# Vector(-3.0, -4.0) — __neg__\n",[32,1138,1140,1142,1144,1147,1150],{"class":34,"line":1139},92,[32,1141,1075],{"class":125},[32,1143,921],{"class":74},[32,1145,1146],{"class":125},"abs",[32,1148,1149],{"class":74},"(v1))         ",[32,1151,1152],{"class":38},"# 5.0 — __abs__ via math.hypot\n",[32,1154,1156,1158,1160,1162,1165,1168,1170,1172,1175],{"class":34,"line":1155},93,[32,1157,1075],{"class":125},[32,1159,921],{"class":74},[32,1161,441],{"class":125},[32,1163,1164],{"class":74},"(Vector(",[32,1166,1167],{"class":125},"0",[32,1169,138],{"class":74},[32,1171,1167],{"class":125},[32,1173,1174],{"class":74},")))  ",[32,1176,1177],{"class":38},"# False — zero vector is falsy\n",[32,1179,1181,1183,1185,1187,1189,1191,1193,1195,1198],{"class":34,"line":1180},94,[32,1182,1075],{"class":125},[32,1184,1078],{"class":74},[32,1186,482],{"class":70},[32,1188,610],{"class":74},[32,1190,1036],{"class":125},[32,1192,138],{"class":74},[32,1194,1041],{"class":125},[32,1196,1197],{"class":74},"))  ",[32,1199,1200],{"class":38},"# True — __eq__\n",[32,1202,1204],{"class":34,"line":1203},95,[32,1205,64],{"emptyLinePlaceholder":63},[32,1207,1209],{"class":34,"line":1208},96,[32,1210,1211],{"class":38},"# Hashable → usable as dict keys and set members\n",[32,1213,1215,1218,1220,1223,1226,1229,1232],{"class":34,"line":1214},97,[32,1216,1217],{"class":74},"points ",[32,1219,187],{"class":70},[32,1221,1222],{"class":74}," {v1: ",[32,1224,1225],{"class":113},"\"origin-ref\"",[32,1227,1228],{"class":74},", v2: ",[32,1230,1231],{"class":113},"\"other\"",[32,1233,1234],{"class":74},"}\n",[32,1236,1238,1240,1243,1245,1247,1249,1252],{"class":34,"line":1237},98,[32,1239,1075],{"class":125},[32,1241,1242],{"class":74},"(points[Vector(",[32,1244,1036],{"class":125},[32,1246,138],{"class":74},[32,1248,1041],{"class":125},[32,1250,1251],{"class":74},")])  ",[32,1253,1254],{"class":38},"# \"origin-ref\" — found via __hash__ + __eq__\n",[32,1256,1258],{"class":34,"line":1257},99,[32,1259,64],{"emptyLinePlaceholder":63},[32,1261,1263],{"class":34,"line":1262},100,[32,1264,1265],{"class":38},"# Iterable → unpacking works\n",[32,1267,1269,1272,1274],{"class":34,"line":1268},101,[32,1270,1271],{"class":74},"x, y ",[32,1273,187],{"class":70},[32,1275,1276],{"class":74}," v1\n",[32,1278,1280,1282,1285],{"class":34,"line":1279},102,[32,1281,1075],{"class":125},[32,1283,1284],{"class":74},"(x, y)   ",[32,1286,1287],{"class":38},"# 3.0 4.0\n",[14,1289,1291,1292,1295,1296],{"id":1290},"equality-and-hashing-__eq__-and-__hash__","Equality and Hashing: ",[29,1293,1294],{},"__eq__"," and ",[29,1297,1298],{},"__hash__",[19,1300,1301],{"language":21},[23,1302,1304],{"className":25,"code":1303,"language":21,"meta":27,"style":27},"class Point:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\n    def __repr__(self):\n        return f\"Point({self.x}, {self.y})\"\n\n    def __eq__(self, other):\n        if not isinstance(other, Point):\n            return NotImplemented        # not False! lets Python try other.__eq__(self)\n        return self.x == other.x and self.y == other.y\n\np1, p2 = Point(1, 2), Point(1, 2)\nprint(p1 == p2)      # True — uses __eq__\nprint(p1 is p2)         # False — different objects in memory\n\ns = {p1}\n# print(p2 in s)      # TypeError: unhashable type: 'Point'\n",[29,1305,1306,1315,1324,1341,1345,1353,1380,1384,1393,1404,1413,1438,1442,1469,1484,1499,1503,1513],{"__ignoreMap":27},[32,1307,1308,1310,1313],{"class":34,"line":35},[32,1309,100],{"class":70},[32,1311,1312],{"class":103}," Point",[32,1314,107],{"class":74},[32,1316,1317,1319,1321],{"class":34,"line":42},[32,1318,158],{"class":70},[32,1320,161],{"class":125},[32,1322,1323],{"class":74},"(self, x, y):\n",[32,1325,1326,1328,1331,1333,1336,1338],{"class":34,"line":48},[32,1327,181],{"class":125},[32,1329,1330],{"class":74},".x, ",[32,1332,543],{"class":125},[32,1334,1335],{"class":74},".y ",[32,1337,187],{"class":70},[32,1339,1340],{"class":74}," x, y\n",[32,1342,1343],{"class":34,"line":54},[32,1344,64],{"emptyLinePlaceholder":63},[32,1346,1347,1349,1351],{"class":34,"line":60},[32,1348,158],{"class":70},[32,1350,227],{"class":125},[32,1352,957],{"class":74},[32,1354,1355,1357,1359,1362,1364,1367,1369,1371,1373,1376,1378],{"class":34,"line":67},[32,1356,247],{"class":70},[32,1358,250],{"class":70},[32,1360,1361],{"class":113},"\"Point(",[32,1363,256],{"class":125},[32,1365,1366],{"class":74},".x",[32,1368,265],{"class":125},[32,1370,138],{"class":113},[32,1372,256],{"class":125},[32,1374,1375],{"class":74},".y",[32,1377,265],{"class":125},[32,1379,279],{"class":113},[32,1381,1382],{"class":34,"line":78},[32,1383,64],{"emptyLinePlaceholder":63},[32,1385,1386,1388,1390],{"class":34,"line":92},[32,1387,158],{"class":70},[32,1389,429],{"class":125},[32,1391,1392],{"class":74},"(self, other):\n",[32,1394,1395,1397,1399,1401],{"class":34,"line":97},[32,1396,449],{"class":70},[32,1398,452],{"class":70},[32,1400,455],{"class":125},[32,1402,1403],{"class":74},"(other, Point):\n",[32,1405,1406,1408,1410],{"class":34,"line":110},[32,1407,464],{"class":70},[32,1409,467],{"class":125},[32,1411,1412],{"class":38},"        # not False! lets Python try other.__eq__(self)\n",[32,1414,1415,1417,1419,1422,1424,1427,1429,1431,1433,1435],{"class":34,"line":117},[32,1416,247],{"class":70},[32,1418,371],{"class":125},[32,1420,1421],{"class":74},".x ",[32,1423,482],{"class":70},[32,1425,1426],{"class":74}," other.x ",[32,1428,488],{"class":70},[32,1430,371],{"class":125},[32,1432,1335],{"class":74},[32,1434,482],{"class":70},[32,1436,1437],{"class":74}," other.y\n",[32,1439,1440],{"class":34,"line":122},[32,1441,64],{"emptyLinePlaceholder":63},[32,1443,1444,1447,1449,1452,1454,1456,1458,1461,1463,1465,1467],{"class":34,"line":150},[32,1445,1446],{"class":74},"p1, p2 ",[32,1448,187],{"class":70},[32,1450,1451],{"class":74}," Point(",[32,1453,1057],{"class":125},[32,1455,138],{"class":74},[32,1457,1062],{"class":125},[32,1459,1460],{"class":74},"), Point(",[32,1462,1057],{"class":125},[32,1464,138],{"class":74},[32,1466,1062],{"class":125},[32,1468,1044],{"class":74},[32,1470,1471,1473,1476,1478,1481],{"class":34,"line":155},[32,1472,1075],{"class":125},[32,1474,1475],{"class":74},"(p1 ",[32,1477,482],{"class":70},[32,1479,1480],{"class":74}," p2)      ",[32,1482,1483],{"class":38},"# True — uses __eq__\n",[32,1485,1486,1488,1490,1493,1496],{"class":34,"line":178},[32,1487,1075],{"class":125},[32,1489,1475],{"class":74},[32,1491,1492],{"class":70},"is",[32,1494,1495],{"class":74}," p2)         ",[32,1497,1498],{"class":38},"# False — different objects in memory\n",[32,1500,1501],{"class":34,"line":196},[32,1502,64],{"emptyLinePlaceholder":63},[32,1504,1505,1508,1510],{"class":34,"line":211},[32,1506,1507],{"class":74},"s ",[32,1509,187],{"class":70},[32,1511,1512],{"class":74}," {p1}\n",[32,1514,1515],{"class":34,"line":216},[32,1516,1517],{"class":38},"# print(p2 in s)      # TypeError: unhashable type: 'Point'\n",[1519,1520,1521,1522,1524,1525,1524,1529,1531,1532,1534,1535,1538],"p",{},"Defining ",[29,1523,1294],{}," ",[1526,1527,1528],"strong",{},"without",[29,1530,1298],{}," makes instances unhashable — Python sets ",[29,1533,1298],{}," to ",[29,1536,1537],{},"None"," automatically in that case, because the default identity-based hash would violate the fundamental invariant that equal objects must have equal hashes (needed for correct dict\u002Fset behavior).",[19,1540,1541],{"language":21},[23,1542,1544],{"className":25,"code":1543,"language":21,"meta":27,"style":27},"class Point:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\n    def __eq__(self, other):\n        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)\n\n    def __hash__(self):\n        return hash((self.x, self.y))     # MUST be consistent with __eq__\n\np1, p2 = Point(1, 2), Point(1, 2)\ns = {p1}\nprint(p2 in s)   # True — same hash, and __eq__ agrees they're equal\n",[29,1545,1546,1554,1562,1576,1580,1588,1615,1619,1627,1647,1651,1675,1683],{"__ignoreMap":27},[32,1547,1548,1550,1552],{"class":34,"line":35},[32,1549,100],{"class":70},[32,1551,1312],{"class":103},[32,1553,107],{"class":74},[32,1555,1556,1558,1560],{"class":34,"line":42},[32,1557,158],{"class":70},[32,1559,161],{"class":125},[32,1561,1323],{"class":74},[32,1563,1564,1566,1568,1570,1572,1574],{"class":34,"line":48},[32,1565,181],{"class":125},[32,1567,1330],{"class":74},[32,1569,543],{"class":125},[32,1571,1335],{"class":74},[32,1573,187],{"class":70},[32,1575,1340],{"class":74},[32,1577,1578],{"class":34,"line":54},[32,1579,64],{"emptyLinePlaceholder":63},[32,1581,1582,1584,1586],{"class":34,"line":60},[32,1583,158],{"class":70},[32,1585,429],{"class":125},[32,1587,1392],{"class":74},[32,1589,1590,1592,1594,1597,1599,1601,1603,1605,1607,1610,1612],{"class":34,"line":67},[32,1591,247],{"class":70},[32,1593,455],{"class":125},[32,1595,1596],{"class":74},"(other, Point) ",[32,1598,488],{"class":70},[32,1600,132],{"class":74},[32,1602,543],{"class":125},[32,1604,1330],{"class":74},[32,1606,543],{"class":125},[32,1608,1609],{"class":74},".y) ",[32,1611,482],{"class":70},[32,1613,1614],{"class":74}," (other.x, other.y)\n",[32,1616,1617],{"class":34,"line":78},[32,1618,64],{"emptyLinePlaceholder":63},[32,1620,1621,1623,1625],{"class":34,"line":92},[32,1622,158],{"class":70},[32,1624,510],{"class":125},[32,1626,957],{"class":74},[32,1628,1629,1631,1633,1635,1637,1639,1641,1644],{"class":34,"line":97},[32,1630,247],{"class":70},[32,1632,537],{"class":125},[32,1634,540],{"class":74},[32,1636,543],{"class":125},[32,1638,1330],{"class":74},[32,1640,543],{"class":125},[32,1642,1643],{"class":74},".y))     ",[32,1645,1646],{"class":38},"# MUST be consistent with __eq__\n",[32,1648,1649],{"class":34,"line":110},[32,1650,64],{"emptyLinePlaceholder":63},[32,1652,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673],{"class":34,"line":117},[32,1654,1446],{"class":74},[32,1656,187],{"class":70},[32,1658,1451],{"class":74},[32,1660,1057],{"class":125},[32,1662,138],{"class":74},[32,1664,1062],{"class":125},[32,1666,1460],{"class":74},[32,1668,1057],{"class":125},[32,1670,138],{"class":74},[32,1672,1062],{"class":125},[32,1674,1044],{"class":74},[32,1676,1677,1679,1681],{"class":34,"line":122},[32,1678,1507],{"class":74},[32,1680,187],{"class":70},[32,1682,1512],{"class":74},[32,1684,1685,1687,1690,1693,1696],{"class":34,"line":150},[32,1686,1075],{"class":125},[32,1688,1689],{"class":74},"(p2 ",[32,1691,1692],{"class":70},"in",[32,1694,1695],{"class":74}," s)   ",[32,1697,1698],{"class":38},"# True — same hash, and __eq__ agrees they're equal\n",[1519,1700,1701,1704,1705,1708,1709,1712],{},[1526,1702,1703],{},"Critical invariant",": if ",[29,1706,1707],{},"a == b",", then ",[29,1710,1711],{},"hash(a) == hash(b)"," must also hold — violating this silently corrupts dict\u002Fset lookups (an object can appear \"missing\" from a set it's actually logically equal to a member of), producing bugs that only manifest under specific hash-bucket collisions and are notoriously hard to reproduce.",[1714,1715,1717],"h3",{"id":1716},"never-make-a-mutable-object-hashable-on-mutable-fields","Never make a mutable object hashable on mutable fields",[19,1719,1720],{"language":21},[23,1721,1723],{"className":25,"code":1722,"language":21,"meta":27,"style":27},"class BadBag:\n    def __init__(self, items):\n        self.items = items                # a MUTABLE list\n\n    def __eq__(self, other):\n        return self.items == other.items\n\n    def __hash__(self):\n        return hash(tuple(self.items))    # hash depends on MUTABLE state\n\nbag = BadBag([1, 2, 3])\ns = {bag}\nprint(bag in s)   # True\n\nbag.items.append(4)    # mutate AFTER inserting into the set\nprint(bag in s)   # False! — its hash changed, so the set looks in the WRONG bucket\nprint(s)   # still contains the (now-unreachable) bag\n",[29,1724,1725,1734,1743,1758,1762,1770,1783,1787,1795,1816,1820,1843,1852,1866,1870,1883,1896],{"__ignoreMap":27},[32,1726,1727,1729,1732],{"class":34,"line":35},[32,1728,100],{"class":70},[32,1730,1731],{"class":103}," BadBag",[32,1733,107],{"class":74},[32,1735,1736,1738,1740],{"class":34,"line":42},[32,1737,158],{"class":70},[32,1739,161],{"class":125},[32,1741,1742],{"class":74},"(self, items):\n",[32,1744,1745,1747,1750,1752,1755],{"class":34,"line":48},[32,1746,181],{"class":125},[32,1748,1749],{"class":74},".items ",[32,1751,187],{"class":70},[32,1753,1754],{"class":74}," items                ",[32,1756,1757],{"class":38},"# a MUTABLE list\n",[32,1759,1760],{"class":34,"line":54},[32,1761,64],{"emptyLinePlaceholder":63},[32,1763,1764,1766,1768],{"class":34,"line":60},[32,1765,158],{"class":70},[32,1767,429],{"class":125},[32,1769,1392],{"class":74},[32,1771,1772,1774,1776,1778,1780],{"class":34,"line":67},[32,1773,247],{"class":70},[32,1775,371],{"class":125},[32,1777,1749],{"class":74},[32,1779,482],{"class":70},[32,1781,1782],{"class":74}," other.items\n",[32,1784,1785],{"class":34,"line":78},[32,1786,64],{"emptyLinePlaceholder":63},[32,1788,1789,1791,1793],{"class":34,"line":92},[32,1790,158],{"class":70},[32,1792,510],{"class":125},[32,1794,957],{"class":74},[32,1796,1797,1799,1801,1803,1806,1808,1810,1813],{"class":34,"line":97},[32,1798,247],{"class":70},[32,1800,537],{"class":125},[32,1802,921],{"class":74},[32,1804,1805],{"class":125},"tuple",[32,1807,921],{"class":74},[32,1809,543],{"class":125},[32,1811,1812],{"class":74},".items))    ",[32,1814,1815],{"class":38},"# hash depends on MUTABLE state\n",[32,1817,1818],{"class":34,"line":110},[32,1819,64],{"emptyLinePlaceholder":63},[32,1821,1822,1825,1827,1830,1832,1834,1836,1838,1840],{"class":34,"line":117},[32,1823,1824],{"class":74},"bag ",[32,1826,187],{"class":70},[32,1828,1829],{"class":74}," BadBag([",[32,1831,1057],{"class":125},[32,1833,138],{"class":74},[32,1835,1062],{"class":125},[32,1837,138],{"class":74},[32,1839,1036],{"class":125},[32,1841,1842],{"class":74},"])\n",[32,1844,1845,1847,1849],{"class":34,"line":122},[32,1846,1507],{"class":74},[32,1848,187],{"class":70},[32,1850,1851],{"class":74}," {bag}\n",[32,1853,1854,1856,1859,1861,1863],{"class":34,"line":150},[32,1855,1075],{"class":125},[32,1857,1858],{"class":74},"(bag ",[32,1860,1692],{"class":70},[32,1862,1695],{"class":74},[32,1864,1865],{"class":38},"# True\n",[32,1867,1868],{"class":34,"line":155},[32,1869,64],{"emptyLinePlaceholder":63},[32,1871,1872,1875,1877,1880],{"class":34,"line":178},[32,1873,1874],{"class":74},"bag.items.append(",[32,1876,1041],{"class":125},[32,1878,1879],{"class":74},")    ",[32,1881,1882],{"class":38},"# mutate AFTER inserting into the set\n",[32,1884,1885,1887,1889,1891,1893],{"class":34,"line":196},[32,1886,1075],{"class":125},[32,1888,1858],{"class":74},[32,1890,1692],{"class":70},[32,1892,1695],{"class":74},[32,1894,1895],{"class":38},"# False! — its hash changed, so the set looks in the WRONG bucket\n",[32,1897,1898,1900,1903],{"class":34,"line":211},[32,1899,1075],{"class":125},[32,1901,1902],{"class":74},"(s)   ",[32,1904,1905],{"class":38},"# still contains the (now-unreachable) bag\n",[1519,1907,1908,1909,1912,1913,1916,1917,1919,1920,1922],{},"This is a genuinely dangerous, silent-corruption bug: the object is still physically in the set (iterating ",[29,1910,1911],{},"s"," shows it), but membership tests and further set operations behave incorrectly because the object no longer hashes to the bucket it was stored under. ",[1526,1914,1915],{},"Rule",": only make a class hashable if its fields used in ",[29,1918,1294],{},"\u002F",[29,1921,1298],{}," are effectively immutable for the object's lifetime.",[14,1924,1926,1927,138,1930,138,1933],{"id":1925},"container-protocols-__len__-__getitem__-__contains__","Container Protocols: ",[29,1928,1929],{},"__len__",[29,1931,1932],{},"__getitem__",[29,1934,1935],{},"__contains__",[19,1937,1938],{"language":21},[23,1939,1941],{"className":25,"code":1940,"language":21,"meta":27,"style":27},"class Deck:\n    def __init__(self):\n        self.cards = [f\"{rank}{suit}\" for suit in \"SHDC\" for rank in \"23456789TJQKA\"]\n\n    def __len__(self):\n        return len(self.cards)\n\n    def __getitem__(self, index):\n        return self.cards[index]      # also enables slicing, `in`, and iteration for free!\n\ndeck = Deck()\nprint(len(deck))          # 52\nprint(deck[0])               # \"2S\"\nprint(deck[-1])                 # \"AC\"\nprint(deck[:3])                    # ['2S', '3S', '4S'] — slicing works automatically\nprint(\"AC\" in deck)                   # True — falls back to linear __getitem__ scan\nfor card in deck[:2]:\n    print(card)                          # iteration ALSO falls back to __getitem__(0), (1), ... until IndexError\n",[29,1942,1943,1952,1960,2018,2022,2030,2044,2048,2058,2070,2074,2084,2099,2114,2130,2145,2163,2181],{"__ignoreMap":27},[32,1944,1945,1947,1950],{"class":34,"line":35},[32,1946,100],{"class":70},[32,1948,1949],{"class":103}," Deck",[32,1951,107],{"class":74},[32,1953,1954,1956,1958],{"class":34,"line":42},[32,1955,158],{"class":70},[32,1957,161],{"class":125},[32,1959,957],{"class":74},[32,1961,1962,1964,1967,1969,1972,1975,1978,1981,1984,1987,1990,1992,1994,1997,2000,2002,2005,2007,2010,2012,2015],{"class":34,"line":48},[32,1963,181],{"class":125},[32,1965,1966],{"class":74},".cards ",[32,1968,187],{"class":70},[32,1970,1971],{"class":74}," [",[32,1973,1974],{"class":70},"f",[32,1976,1977],{"class":113},"\"",[32,1979,1980],{"class":125},"{",[32,1982,1983],{"class":74},"rank",[32,1985,1986],{"class":125},"}{",[32,1988,1989],{"class":74},"suit",[32,1991,265],{"class":125},[32,1993,1977],{"class":113},[32,1995,1996],{"class":70}," for",[32,1998,1999],{"class":74}," suit ",[32,2001,1692],{"class":70},[32,2003,2004],{"class":113}," \"SHDC\"",[32,2006,1996],{"class":70},[32,2008,2009],{"class":74}," rank ",[32,2011,1692],{"class":70},[32,2013,2014],{"class":113}," \"23456789TJQKA\"",[32,2016,2017],{"class":74},"]\n",[32,2019,2020],{"class":34,"line":54},[32,2021,64],{"emptyLinePlaceholder":63},[32,2023,2024,2026,2028],{"class":34,"line":60},[32,2025,158],{"class":70},[32,2027,995],{"class":125},[32,2029,957],{"class":74},[32,2031,2032,2034,2037,2039,2041],{"class":34,"line":67},[32,2033,247],{"class":70},[32,2035,2036],{"class":125}," len",[32,2038,921],{"class":74},[32,2040,543],{"class":125},[32,2042,2043],{"class":74},".cards)\n",[32,2045,2046],{"class":34,"line":78},[32,2047,64],{"emptyLinePlaceholder":63},[32,2049,2050,2052,2055],{"class":34,"line":92},[32,2051,158],{"class":70},[32,2053,2054],{"class":125}," __getitem__",[32,2056,2057],{"class":74},"(self, index):\n",[32,2059,2060,2062,2064,2067],{"class":34,"line":97},[32,2061,247],{"class":70},[32,2063,371],{"class":125},[32,2065,2066],{"class":74},".cards[index]      ",[32,2068,2069],{"class":38},"# also enables slicing, `in`, and iteration for free!\n",[32,2071,2072],{"class":34,"line":110},[32,2073,64],{"emptyLinePlaceholder":63},[32,2075,2076,2079,2081],{"class":34,"line":117},[32,2077,2078],{"class":74},"deck ",[32,2080,187],{"class":70},[32,2082,2083],{"class":74}," Deck()\n",[32,2085,2086,2088,2090,2093,2096],{"class":34,"line":122},[32,2087,1075],{"class":125},[32,2089,921],{"class":74},[32,2091,2092],{"class":125},"len",[32,2094,2095],{"class":74},"(deck))          ",[32,2097,2098],{"class":38},"# 52\n",[32,2100,2101,2103,2106,2108,2111],{"class":34,"line":150},[32,2102,1075],{"class":125},[32,2104,2105],{"class":74},"(deck[",[32,2107,1167],{"class":125},[32,2109,2110],{"class":74},"])               ",[32,2112,2113],{"class":38},"# \"2S\"\n",[32,2115,2116,2118,2120,2122,2124,2127],{"class":34,"line":155},[32,2117,1075],{"class":125},[32,2119,2105],{"class":74},[32,2121,835],{"class":70},[32,2123,1057],{"class":125},[32,2125,2126],{"class":74},"])                 ",[32,2128,2129],{"class":38},"# \"AC\"\n",[32,2131,2132,2134,2137,2139,2142],{"class":34,"line":178},[32,2133,1075],{"class":125},[32,2135,2136],{"class":74},"(deck[:",[32,2138,1036],{"class":125},[32,2140,2141],{"class":74},"])                    ",[32,2143,2144],{"class":38},"# ['2S', '3S', '4S'] — slicing works automatically\n",[32,2146,2147,2149,2151,2154,2157,2160],{"class":34,"line":196},[32,2148,1075],{"class":125},[32,2150,921],{"class":74},[32,2152,2153],{"class":113},"\"AC\"",[32,2155,2156],{"class":70}," in",[32,2158,2159],{"class":74}," deck)                   ",[32,2161,2162],{"class":38},"# True — falls back to linear __getitem__ scan\n",[32,2164,2165,2168,2171,2173,2176,2178],{"class":34,"line":211},[32,2166,2167],{"class":70},"for",[32,2169,2170],{"class":74}," card ",[32,2172,1692],{"class":70},[32,2174,2175],{"class":74}," deck[:",[32,2177,1062],{"class":125},[32,2179,2180],{"class":74},"]:\n",[32,2182,2183,2186,2189],{"class":34,"line":216},[32,2184,2185],{"class":125},"    print",[32,2187,2188],{"class":74},"(card)                          ",[32,2190,2191],{"class":38},"# iteration ALSO falls back to __getitem__(0), (1), ... until IndexError\n",[1519,2193,2194,2195,2197,2198,138,2200,2202,2203,2206,2207,138,2210,2213,2214,2217,2218,2221,2222,2224],{},"Implementing just ",[29,2196,1932],{}," gives you ",[29,2199,2167],{},[29,2201,1692],{},", slicing, and ",[29,2204,2205],{},"reversed()"," almost for free, because Python's iteration protocol falls back to repeatedly calling ",[29,2208,2209],{},"__getitem__(0)",[29,2211,2212],{},"__getitem__(1)",", ... until ",[29,2215,2216],{},"IndexError"," is raised, if ",[29,2219,2220],{},"__iter__"," isn't defined. This is a legacy fallback (pre-dates the formal iterator protocol) — for anything beyond a simple sequence, define ",[29,2223,2220],{}," explicitly (see chapter 19).",[14,2226,2228,2229],{"id":2227},"making-an-object-iterable-__iter__","Making an Object Iterable: ",[29,2230,2220],{},[19,2232,2233],{"language":21},[23,2234,2236],{"className":25,"code":2235,"language":21,"meta":27,"style":27},"class Countdown:\n    def __init__(self, start):\n        self.start = start\n\n    def __iter__(self):\n        n = self.start\n        while n > 0:\n            yield n            # __iter__ as a generator function — simplest way to implement it\n            n -= 1\n\nfor i in Countdown(3):\n    print(i)   # 3 2 1\n\nprint(list(Countdown(5)))   # [5, 4, 3, 2, 1]\nprint(list(Countdown(5)))     # [5, 4, 3, 2, 1] — a NEW generator each time __iter__ is called, reusable!\n",[29,2237,2238,2247,2256,2268,2272,2280,2292,2308,2319,2330,2334,2350,2360,2364,2385],{"__ignoreMap":27},[32,2239,2240,2242,2245],{"class":34,"line":35},[32,2241,100],{"class":70},[32,2243,2244],{"class":103}," Countdown",[32,2246,107],{"class":74},[32,2248,2249,2251,2253],{"class":34,"line":42},[32,2250,158],{"class":70},[32,2252,161],{"class":125},[32,2254,2255],{"class":74},"(self, start):\n",[32,2257,2258,2260,2263,2265],{"class":34,"line":48},[32,2259,181],{"class":125},[32,2261,2262],{"class":74},".start ",[32,2264,187],{"class":70},[32,2266,2267],{"class":74}," start\n",[32,2269,2270],{"class":34,"line":54},[32,2271,64],{"emptyLinePlaceholder":63},[32,2273,2274,2276,2278],{"class":34,"line":60},[32,2275,158],{"class":70},[32,2277,954],{"class":125},[32,2279,957],{"class":74},[32,2281,2282,2285,2287,2289],{"class":34,"line":67},[32,2283,2284],{"class":74},"        n ",[32,2286,187],{"class":70},[32,2288,371],{"class":125},[32,2290,2291],{"class":74},".start\n",[32,2293,2294,2297,2300,2303,2306],{"class":34,"line":78},[32,2295,2296],{"class":70},"        while",[32,2298,2299],{"class":74}," n ",[32,2301,2302],{"class":70},">",[32,2304,2305],{"class":125}," 0",[32,2307,107],{"class":74},[32,2309,2310,2313,2316],{"class":34,"line":92},[32,2311,2312],{"class":70},"            yield",[32,2314,2315],{"class":74}," n            ",[32,2317,2318],{"class":38},"# __iter__ as a generator function — simplest way to implement it\n",[32,2320,2321,2324,2327],{"class":34,"line":97},[32,2322,2323],{"class":74},"            n ",[32,2325,2326],{"class":70},"-=",[32,2328,2329],{"class":125}," 1\n",[32,2331,2332],{"class":34,"line":110},[32,2333,64],{"emptyLinePlaceholder":63},[32,2335,2336,2338,2341,2343,2346,2348],{"class":34,"line":117},[32,2337,2167],{"class":70},[32,2339,2340],{"class":74}," i ",[32,2342,1692],{"class":70},[32,2344,2345],{"class":74}," Countdown(",[32,2347,1036],{"class":125},[32,2349,175],{"class":74},[32,2351,2352,2354,2357],{"class":34,"line":122},[32,2353,2185],{"class":125},[32,2355,2356],{"class":74},"(i)   ",[32,2358,2359],{"class":38},"# 3 2 1\n",[32,2361,2362],{"class":34,"line":150},[32,2363,64],{"emptyLinePlaceholder":63},[32,2365,2366,2368,2370,2373,2376,2379,2382],{"class":34,"line":155},[32,2367,1075],{"class":125},[32,2369,921],{"class":74},[32,2371,2372],{"class":125},"list",[32,2374,2375],{"class":74},"(Countdown(",[32,2377,2378],{"class":125},"5",[32,2380,2381],{"class":74},")))   ",[32,2383,2384],{"class":38},"# [5, 4, 3, 2, 1]\n",[32,2386,2387,2389,2391,2393,2395,2397,2400],{"class":34,"line":178},[32,2388,1075],{"class":125},[32,2390,921],{"class":74},[32,2392,2372],{"class":125},[32,2394,2375],{"class":74},[32,2396,2378],{"class":125},[32,2398,2399],{"class":74},")))     ",[32,2401,2402],{"class":38},"# [5, 4, 3, 2, 1] — a NEW generator each time __iter__ is called, reusable!\n",[1519,2404,2405,2406,2408,2409,2411,2412,2415,2416,2418,2419,2421],{},"Unlike a bare generator object (chapter 09), a class implementing ",[29,2407,2220],{}," as a generator function is reusable across multiple ",[29,2410,2167],{}," loops — each call to ",[29,2413,2414],{},"iter(obj)"," (which ",[29,2417,2167],{}," does implicitly) invokes ",[29,2420,2220],{}," fresh, producing a brand-new generator each time.",[14,2423,2425],{"id":2424},"operator-overloading","Operator Overloading",[19,2427,2428],{"language":21},[23,2429,2431],{"className":25,"code":2430,"language":21,"meta":27,"style":27},"class Vector:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\n    def __repr__(self):\n        return f\"Vector({self.x}, {self.y})\"\n\n    def __add__(self, other):\n        return Vector(self.x + other.x, self.y + other.y)\n\n    def __sub__(self, other):\n        return Vector(self.x - other.x, self.y - other.y)\n\n    def __mul__(self, scalar):\n        return Vector(self.x * scalar, self.y * scalar)\n\n    def __rmul__(self, scalar):            # handles scalar * vector (reflected operand order)\n        return self.__mul__(scalar)\n\n    def __neg__(self):\n        return Vector(-self.x, -self.y)\n\n    def __eq__(self, other):\n        return isinstance(other, Vector) and (self.x, self.y) == (other.x, other.y)\n\nv1, v2 = Vector(1, 2), Vector(3, 4)\nprint(v1 + v2)     # Vector(4, 6)\nprint(v1 - v2)       # Vector(-2, -2)\nprint(v1 * 3)          # Vector(3, 6)   — calls v1.__mul__(3)\nprint(3 * v1)             # Vector(3, 6)   — v1.__mul__ doesn't apply (int has no __mul__ for Vector),\n                            # so Python tries v1.__rmul__(3) instead\nprint(-v1)                   # Vector(-1, -2)\n",[29,2432,2433,2441,2449,2463,2467,2475,2499,2503,2511,2535,2539,2548,2570,2574,2583,2605,2609,2621,2634,2638,2646,2665,2669,2677,2702,2706,2732,2746,2760,2776,2792,2797],{"__ignoreMap":27},[32,2434,2435,2437,2439],{"class":34,"line":35},[32,2436,100],{"class":70},[32,2438,104],{"class":103},[32,2440,107],{"class":74},[32,2442,2443,2445,2447],{"class":34,"line":42},[32,2444,158],{"class":70},[32,2446,161],{"class":125},[32,2448,1323],{"class":74},[32,2450,2451,2453,2455,2457,2459,2461],{"class":34,"line":48},[32,2452,181],{"class":125},[32,2454,1330],{"class":74},[32,2456,543],{"class":125},[32,2458,1335],{"class":74},[32,2460,187],{"class":70},[32,2462,1340],{"class":74},[32,2464,2465],{"class":34,"line":54},[32,2466,64],{"emptyLinePlaceholder":63},[32,2468,2469,2471,2473],{"class":34,"line":60},[32,2470,158],{"class":70},[32,2472,227],{"class":125},[32,2474,957],{"class":74},[32,2476,2477,2479,2481,2483,2485,2487,2489,2491,2493,2495,2497],{"class":34,"line":67},[32,2478,247],{"class":70},[32,2480,250],{"class":70},[32,2482,253],{"class":113},[32,2484,256],{"class":125},[32,2486,1366],{"class":74},[32,2488,265],{"class":125},[32,2490,138],{"class":113},[32,2492,256],{"class":125},[32,2494,1375],{"class":74},[32,2496,265],{"class":125},[32,2498,279],{"class":113},[32,2500,2501],{"class":34,"line":78},[32,2502,64],{"emptyLinePlaceholder":63},[32,2504,2505,2507,2509],{"class":34,"line":92},[32,2506,158],{"class":70},[32,2508,570],{"class":125},[32,2510,1392],{"class":74},[32,2512,2513,2515,2517,2519,2521,2523,2526,2528,2530,2532],{"class":34,"line":97},[32,2514,247],{"class":70},[32,2516,610],{"class":74},[32,2518,543],{"class":125},[32,2520,1421],{"class":74},[32,2522,617],{"class":70},[32,2524,2525],{"class":74}," other.x, ",[32,2527,543],{"class":125},[32,2529,1335],{"class":74},[32,2531,617],{"class":70},[32,2533,2534],{"class":74}," other.y)\n",[32,2536,2537],{"class":34,"line":110},[32,2538,64],{"emptyLinePlaceholder":63},[32,2540,2541,2543,2546],{"class":34,"line":117},[32,2542,158],{"class":70},[32,2544,2545],{"class":125}," __sub__",[32,2547,1392],{"class":74},[32,2549,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568],{"class":34,"line":122},[32,2551,247],{"class":70},[32,2553,610],{"class":74},[32,2555,543],{"class":125},[32,2557,1421],{"class":74},[32,2559,835],{"class":70},[32,2561,2525],{"class":74},[32,2563,543],{"class":125},[32,2565,1335],{"class":74},[32,2567,835],{"class":70},[32,2569,2534],{"class":74},[32,2571,2572],{"class":34,"line":150},[32,2573,64],{"emptyLinePlaceholder":63},[32,2575,2576,2578,2580],{"class":34,"line":155},[32,2577,158],{"class":70},[32,2579,699],{"class":125},[32,2581,2582],{"class":74},"(self, scalar):\n",[32,2584,2585,2587,2589,2591,2593,2595,2597,2599,2601,2603],{"class":34,"line":178},[32,2586,247],{"class":70},[32,2588,610],{"class":74},[32,2590,543],{"class":125},[32,2592,1421],{"class":74},[32,2594,753],{"class":70},[32,2596,756],{"class":74},[32,2598,543],{"class":125},[32,2600,1335],{"class":74},[32,2602,753],{"class":70},[32,2604,765],{"class":74},[32,2606,2607],{"class":34,"line":196},[32,2608,64],{"emptyLinePlaceholder":63},[32,2610,2611,2613,2615,2618],{"class":34,"line":211},[32,2612,158],{"class":70},[32,2614,778],{"class":125},[32,2616,2617],{"class":74},"(self, scalar):            ",[32,2619,2620],{"class":38},"# handles scalar * vector (reflected operand order)\n",[32,2622,2623,2625,2627,2629,2631],{"class":34,"line":216},[32,2624,247],{"class":70},[32,2626,371],{"class":125},[32,2628,674],{"class":74},[32,2630,800],{"class":125},[32,2632,2633],{"class":74},"(scalar)\n",[32,2635,2636],{"class":34,"line":222},[32,2637,64],{"emptyLinePlaceholder":63},[32,2639,2640,2642,2644],{"class":34,"line":238},[32,2641,158],{"class":70},[32,2643,819],{"class":125},[32,2645,957],{"class":74},[32,2647,2648,2650,2652,2654,2656,2658,2660,2662],{"class":34,"line":244},[32,2649,247],{"class":70},[32,2651,610],{"class":74},[32,2653,835],{"class":70},[32,2655,543],{"class":125},[32,2657,1330],{"class":74},[32,2659,835],{"class":70},[32,2661,543],{"class":125},[32,2663,2664],{"class":74},".y)\n",[32,2666,2667],{"class":34,"line":282},[32,2668,64],{"emptyLinePlaceholder":63},[32,2670,2671,2673,2675],{"class":34,"line":287},[32,2672,158],{"class":70},[32,2674,429],{"class":125},[32,2676,1392],{"class":74},[32,2678,2679,2681,2683,2686,2688,2690,2692,2694,2696,2698,2700],{"class":34,"line":301},[32,2680,247],{"class":70},[32,2682,455],{"class":125},[32,2684,2685],{"class":74},"(other, Vector) ",[32,2687,488],{"class":70},[32,2689,132],{"class":74},[32,2691,543],{"class":125},[32,2693,1330],{"class":74},[32,2695,543],{"class":125},[32,2697,1609],{"class":74},[32,2699,482],{"class":70},[32,2701,1614],{"class":74},[32,2703,2704],{"class":34,"line":332},[32,2705,64],{"emptyLinePlaceholder":63},[32,2707,2708,2711,2713,2715,2717,2719,2721,2724,2726,2728,2730],{"class":34,"line":337},[32,2709,2710],{"class":74},"v1, v2 ",[32,2712,187],{"class":70},[32,2714,610],{"class":74},[32,2716,1057],{"class":125},[32,2718,138],{"class":74},[32,2720,1062],{"class":125},[32,2722,2723],{"class":74},"), Vector(",[32,2725,1036],{"class":125},[32,2727,138],{"class":74},[32,2729,1041],{"class":125},[32,2731,1044],{"class":74},[32,2733,2734,2736,2738,2740,2743],{"class":34,"line":343},[32,2735,1075],{"class":125},[32,2737,1078],{"class":74},[32,2739,617],{"class":70},[32,2741,2742],{"class":74}," v2)     ",[32,2744,2745],{"class":38},"# Vector(4, 6)\n",[32,2747,2748,2750,2752,2754,2757],{"class":34,"line":352},[32,2749,1075],{"class":125},[32,2751,1078],{"class":74},[32,2753,835],{"class":70},[32,2755,2756],{"class":74}," v2)       ",[32,2758,2759],{"class":38},"# Vector(-2, -2)\n",[32,2761,2762,2764,2766,2768,2771,2773],{"class":34,"line":366},[32,2763,1075],{"class":125},[32,2765,1078],{"class":74},[32,2767,753],{"class":70},[32,2769,2770],{"class":125}," 3",[32,2772,1100],{"class":74},[32,2774,2775],{"class":38},"# Vector(3, 6)   — calls v1.__mul__(3)\n",[32,2777,2778,2780,2782,2784,2786,2789],{"class":34,"line":377},[32,2779,1075],{"class":125},[32,2781,921],{"class":74},[32,2783,1036],{"class":125},[32,2785,1115],{"class":70},[32,2787,2788],{"class":74}," v1)             ",[32,2790,2791],{"class":38},"# Vector(3, 6)   — v1.__mul__ doesn't apply (int has no __mul__ for Vector),\n",[32,2793,2794],{"class":34,"line":382},[32,2795,2796],{"class":38},"                            # so Python tries v1.__rmul__(3) instead\n",[32,2798,2799,2801,2803,2805,2808],{"class":34,"line":389},[32,2800,1075],{"class":125},[32,2802,921],{"class":74},[32,2804,835],{"class":70},[32,2806,2807],{"class":74},"v1)                   ",[32,2809,2810],{"class":38},"# Vector(-1, -2)\n",[1519,2812,2813,2816,2817,138,2820,2823,2824,2827,2828,2831,2832,2835,2836,2839,2840,2843,2844,2847,2848,2851],{},[29,2814,2815],{},"__rmul__"," (and ",[29,2818,2819],{},"__radd__",[29,2821,2822],{},"__rsub__",", etc.) exist because ",[29,2825,2826],{},"a * b"," first tries ",[29,2829,2830],{},"a.__mul__(b)","; if that returns ",[29,2833,2834],{},"NotImplemented"," (or doesn't exist), Python tries ",[29,2837,2838],{},"b.__rmul__(a)"," as a fallback — this is how ",[29,2841,2842],{},"3 * v1"," works even though ",[29,2845,2846],{},"int.__mul__"," has no idea what a ",[29,2849,2850],{},"Vector"," is.",[14,2853,2855,2856,1295,2859],{"id":2854},"context-managers-__enter__-and-__exit__","Context Managers: ",[29,2857,2858],{},"__enter__",[29,2860,2861],{},"__exit__",[19,2863,2864],{"language":21},[23,2865,2867],{"className":25,"code":2866,"language":21,"meta":27,"style":27},"import time\n\nclass Timer:\n    def __enter__(self):\n        self.start = time.perf_counter()\n        return self               # the value bound to `as` in `with ... as x`\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        self.elapsed = time.perf_counter() - self.start\n        print(f\"Elapsed: {self.elapsed:.4f}s\")\n        return False               # False (or None) means: don't suppress exceptions\n\nwith Timer() as t:\n    total = sum(range(10_000_000))\nprint(t.elapsed)   # accessible after the block too, since `t` is still in scope\n",[29,2868,2869,2876,2880,2889,2898,2909,2918,2922,2932,2950,2977,2987,2991,3005,3028],{"__ignoreMap":27},[32,2870,2871,2873],{"class":34,"line":35},[32,2872,71],{"class":70},[32,2874,2875],{"class":74}," time\n",[32,2877,2878],{"class":34,"line":42},[32,2879,64],{"emptyLinePlaceholder":63},[32,2881,2882,2884,2887],{"class":34,"line":48},[32,2883,100],{"class":70},[32,2885,2886],{"class":103}," Timer",[32,2888,107],{"class":74},[32,2890,2891,2893,2896],{"class":34,"line":54},[32,2892,158],{"class":70},[32,2894,2895],{"class":125}," __enter__",[32,2897,957],{"class":74},[32,2899,2900,2902,2904,2906],{"class":34,"line":60},[32,2901,181],{"class":125},[32,2903,2262],{"class":74},[32,2905,187],{"class":70},[32,2907,2908],{"class":74}," time.perf_counter()\n",[32,2910,2911,2913,2915],{"class":34,"line":67},[32,2912,247],{"class":70},[32,2914,371],{"class":125},[32,2916,2917],{"class":38},"               # the value bound to `as` in `with ... as x`\n",[32,2919,2920],{"class":34,"line":78},[32,2921,64],{"emptyLinePlaceholder":63},[32,2923,2924,2926,2929],{"class":34,"line":92},[32,2925,158],{"class":70},[32,2927,2928],{"class":125}," __exit__",[32,2930,2931],{"class":74},"(self, exc_type, exc_value, traceback):\n",[32,2933,2934,2936,2939,2941,2944,2946,2948],{"class":34,"line":97},[32,2935,181],{"class":125},[32,2937,2938],{"class":74},".elapsed ",[32,2940,187],{"class":70},[32,2942,2943],{"class":74}," time.perf_counter() ",[32,2945,835],{"class":70},[32,2947,371],{"class":125},[32,2949,2291],{"class":74},[32,2951,2952,2955,2957,2959,2962,2964,2967,2970,2972,2975],{"class":34,"line":110},[32,2953,2954],{"class":125},"        print",[32,2956,921],{"class":74},[32,2958,1974],{"class":70},[32,2960,2961],{"class":113},"\"Elapsed: ",[32,2963,256],{"class":125},[32,2965,2966],{"class":74},".elapsed",[32,2968,2969],{"class":70},":.4f",[32,2971,265],{"class":125},[32,2973,2974],{"class":113},"s\"",[32,2976,1044],{"class":74},[32,2978,2979,2981,2984],{"class":34,"line":117},[32,2980,247],{"class":70},[32,2982,2983],{"class":125}," False",[32,2985,2986],{"class":38},"               # False (or None) means: don't suppress exceptions\n",[32,2988,2989],{"class":34,"line":122},[32,2990,64],{"emptyLinePlaceholder":63},[32,2992,2993,2996,2999,3002],{"class":34,"line":150},[32,2994,2995],{"class":70},"with",[32,2997,2998],{"class":74}," Timer() ",[32,3000,3001],{"class":70},"as",[32,3003,3004],{"class":74}," t:\n",[32,3006,3007,3010,3012,3015,3017,3020,3022,3025],{"class":34,"line":155},[32,3008,3009],{"class":74},"    total ",[32,3011,187],{"class":70},[32,3013,3014],{"class":125}," sum",[32,3016,921],{"class":74},[32,3018,3019],{"class":125},"range",[32,3021,921],{"class":74},[32,3023,3024],{"class":125},"10_000_000",[32,3026,3027],{"class":74},"))\n",[32,3029,3030,3032,3035],{"class":34,"line":178},[32,3031,1075],{"class":125},[32,3033,3034],{"class":74},"(t.elapsed)   ",[32,3036,3037],{"class":38},"# accessible after the block too, since `t` is still in scope\n",[1519,3039,3040,3042,3043,3045,3046,1524,3048,3051],{},[29,3041,2861],{}," receives exception info (type, value, traceback) if the ",[29,3044,2995],{}," block raised — returning a truthy value from ",[29,3047,2861],{},[1526,3049,3050],{},"suppresses"," the exception (as if it never happened), which is powerful but dangerous if done carelessly.",[19,3053,3054],{"language":21},[23,3055,3057],{"className":25,"code":3056,"language":21,"meta":27,"style":27},"class SuppressValueError:\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        if exc_type is ValueError:\n            print(f\"Suppressed: {exc_value}\")\n            return True         # True = swallow the ValueError, execution continues after the `with`\n        return False              # any other exception propagates normally\n\nwith SuppressValueError():\n    print(\"before\")\n    int(\"not a number\")             # raises ValueError\n    print(\"never reached\")\n\nprint(\"after the with block\")   # this DOES run — exception was suppressed\n",[29,3058,3059,3068,3076,3083,3087,3095,3109,3132,3142,3151,3155,3162,3173,3189,3200,3204],{"__ignoreMap":27},[32,3060,3061,3063,3066],{"class":34,"line":35},[32,3062,100],{"class":70},[32,3064,3065],{"class":103}," SuppressValueError",[32,3067,107],{"class":74},[32,3069,3070,3072,3074],{"class":34,"line":42},[32,3071,158],{"class":70},[32,3073,2895],{"class":125},[32,3075,957],{"class":74},[32,3077,3078,3080],{"class":34,"line":48},[32,3079,247],{"class":70},[32,3081,3082],{"class":125}," self\n",[32,3084,3085],{"class":34,"line":54},[32,3086,64],{"emptyLinePlaceholder":63},[32,3088,3089,3091,3093],{"class":34,"line":60},[32,3090,158],{"class":70},[32,3092,2928],{"class":125},[32,3094,2931],{"class":74},[32,3096,3097,3099,3102,3104,3107],{"class":34,"line":67},[32,3098,449],{"class":70},[32,3100,3101],{"class":74}," exc_type ",[32,3103,1492],{"class":70},[32,3105,3106],{"class":125}," ValueError",[32,3108,107],{"class":74},[32,3110,3111,3114,3116,3118,3121,3123,3126,3128,3130],{"class":34,"line":78},[32,3112,3113],{"class":125},"            print",[32,3115,921],{"class":74},[32,3117,1974],{"class":70},[32,3119,3120],{"class":113},"\"Suppressed: ",[32,3122,1980],{"class":125},[32,3124,3125],{"class":74},"exc_value",[32,3127,265],{"class":125},[32,3129,1977],{"class":113},[32,3131,1044],{"class":74},[32,3133,3134,3136,3139],{"class":34,"line":92},[32,3135,464],{"class":70},[32,3137,3138],{"class":125}," True",[32,3140,3141],{"class":38},"         # True = swallow the ValueError, execution continues after the `with`\n",[32,3143,3144,3146,3148],{"class":34,"line":97},[32,3145,247],{"class":70},[32,3147,2983],{"class":125},[32,3149,3150],{"class":38},"              # any other exception propagates normally\n",[32,3152,3153],{"class":34,"line":110},[32,3154,64],{"emptyLinePlaceholder":63},[32,3156,3157,3159],{"class":34,"line":117},[32,3158,2995],{"class":70},[32,3160,3161],{"class":74}," SuppressValueError():\n",[32,3163,3164,3166,3168,3171],{"class":34,"line":122},[32,3165,2185],{"class":125},[32,3167,921],{"class":74},[32,3169,3170],{"class":113},"\"before\"",[32,3172,1044],{"class":74},[32,3174,3175,3178,3180,3183,3186],{"class":34,"line":150},[32,3176,3177],{"class":125},"    int",[32,3179,921],{"class":74},[32,3181,3182],{"class":113},"\"not a number\"",[32,3184,3185],{"class":74},")             ",[32,3187,3188],{"class":38},"# raises ValueError\n",[32,3190,3191,3193,3195,3198],{"class":34,"line":155},[32,3192,2185],{"class":125},[32,3194,921],{"class":74},[32,3196,3197],{"class":113},"\"never reached\"",[32,3199,1044],{"class":74},[32,3201,3202],{"class":34,"line":178},[32,3203,64],{"emptyLinePlaceholder":63},[32,3205,3206,3208,3210,3213,3215],{"class":34,"line":196},[32,3207,1075],{"class":125},[32,3209,921],{"class":74},[32,3211,3212],{"class":113},"\"after the with block\"",[32,3214,144],{"class":74},[32,3216,3217],{"class":38},"# this DOES run — exception was suppressed\n",[1519,3219,3220,3223,3224,3226,3227,3230],{},[1526,3221,3222],{},"Best practice",": only suppress exceptions in ",[29,3225,2861],{}," for a very specific, documented, narrow exception type — silently swallowing broad exception categories hides real bugs, exactly like a bare ",[29,3228,3229],{},"except:"," does (chapter 16).",[1714,3232,3234],{"id":3233},"the-database-connection-pattern","The database-connection pattern",[19,3236,3237],{"language":21},[23,3238,3240],{"className":25,"code":3239,"language":21,"meta":27,"style":27},"class DatabaseConnection:\n    def __init__(self, dsn):\n        self.dsn = dsn\n        self.connection = None\n\n    def __enter__(self):\n        print(f\"Connecting to {self.dsn}\")\n        self.connection = object()    # placeholder for a real connection\n        return self.connection\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        print(\"Closing connection\")\n        self.connection = None        # cleanup runs even if the block raised\n        return False\n\nwith DatabaseConnection(\"postgres:\u002F\u002Flocalhost\u002Fapp\") as conn:\n    print(\"Running query\")\n    # raise RuntimeError(\"query failed\")   # __exit__ STILL runs cleanup, then re-raises\n",[29,3241,3242,3251,3260,3272,3284,3288,3296,3318,3335,3344,3348,3356,3367,3381,3388,3392,3410,3421],{"__ignoreMap":27},[32,3243,3244,3246,3249],{"class":34,"line":35},[32,3245,100],{"class":70},[32,3247,3248],{"class":103}," DatabaseConnection",[32,3250,107],{"class":74},[32,3252,3253,3255,3257],{"class":34,"line":42},[32,3254,158],{"class":70},[32,3256,161],{"class":125},[32,3258,3259],{"class":74},"(self, dsn):\n",[32,3261,3262,3264,3267,3269],{"class":34,"line":48},[32,3263,181],{"class":125},[32,3265,3266],{"class":74},".dsn ",[32,3268,187],{"class":70},[32,3270,3271],{"class":74}," dsn\n",[32,3273,3274,3276,3279,3281],{"class":34,"line":54},[32,3275,181],{"class":125},[32,3277,3278],{"class":74},".connection ",[32,3280,187],{"class":70},[32,3282,3283],{"class":125}," None\n",[32,3285,3286],{"class":34,"line":60},[32,3287,64],{"emptyLinePlaceholder":63},[32,3289,3290,3292,3294],{"class":34,"line":67},[32,3291,158],{"class":70},[32,3293,2895],{"class":125},[32,3295,957],{"class":74},[32,3297,3298,3300,3302,3304,3307,3309,3312,3314,3316],{"class":34,"line":78},[32,3299,2954],{"class":125},[32,3301,921],{"class":74},[32,3303,1974],{"class":70},[32,3305,3306],{"class":113},"\"Connecting to ",[32,3308,256],{"class":125},[32,3310,3311],{"class":74},".dsn",[32,3313,265],{"class":125},[32,3315,1977],{"class":113},[32,3317,1044],{"class":74},[32,3319,3320,3322,3324,3326,3329,3332],{"class":34,"line":92},[32,3321,181],{"class":125},[32,3323,3278],{"class":74},[32,3325,187],{"class":70},[32,3327,3328],{"class":125}," object",[32,3330,3331],{"class":74},"()    ",[32,3333,3334],{"class":38},"# placeholder for a real connection\n",[32,3336,3337,3339,3341],{"class":34,"line":97},[32,3338,247],{"class":70},[32,3340,371],{"class":125},[32,3342,3343],{"class":74},".connection\n",[32,3345,3346],{"class":34,"line":110},[32,3347,64],{"emptyLinePlaceholder":63},[32,3349,3350,3352,3354],{"class":34,"line":117},[32,3351,158],{"class":70},[32,3353,2928],{"class":125},[32,3355,2931],{"class":74},[32,3357,3358,3360,3362,3365],{"class":34,"line":122},[32,3359,2954],{"class":125},[32,3361,921],{"class":74},[32,3363,3364],{"class":113},"\"Closing connection\"",[32,3366,1044],{"class":74},[32,3368,3369,3371,3373,3375,3378],{"class":34,"line":150},[32,3370,181],{"class":125},[32,3372,3278],{"class":74},[32,3374,187],{"class":70},[32,3376,3377],{"class":125}," None",[32,3379,3380],{"class":38},"        # cleanup runs even if the block raised\n",[32,3382,3383,3385],{"class":34,"line":155},[32,3384,247],{"class":70},[32,3386,3387],{"class":125}," False\n",[32,3389,3390],{"class":34,"line":178},[32,3391,64],{"emptyLinePlaceholder":63},[32,3393,3394,3396,3399,3402,3405,3407],{"class":34,"line":196},[32,3395,2995],{"class":70},[32,3397,3398],{"class":74}," DatabaseConnection(",[32,3400,3401],{"class":113},"\"postgres:\u002F\u002Flocalhost\u002Fapp\"",[32,3403,3404],{"class":74},") ",[32,3406,3001],{"class":70},[32,3408,3409],{"class":74}," conn:\n",[32,3411,3412,3414,3416,3419],{"class":34,"line":211},[32,3413,2185],{"class":125},[32,3415,921],{"class":74},[32,3417,3418],{"class":113},"\"Running query\"",[32,3420,1044],{"class":74},[32,3422,3423],{"class":34,"line":216},[32,3424,3425],{"class":38},"    # raise RuntimeError(\"query failed\")   # __exit__ STILL runs cleanup, then re-raises\n",[1519,3427,3428,3430,3431,1919,3434,3437,3438,3441],{},[29,3429,2861],{}," is guaranteed to run whether the block completes normally, returns early, or raises — this is the object-oriented equivalent of ",[29,3432,3433],{},"try",[29,3435,3436],{},"finally",", and is exactly what ",[29,3439,3440],{},"with open(...)"," uses under the hood to guarantee file handles get closed.",[14,3443,3445,3448],{"id":3444},"__call__-making-instances-callable",[29,3446,3447],{},"__call__"," — Making Instances Callable",[19,3450,3451],{"language":21},[23,3452,3454],{"className":25,"code":3453,"language":21,"meta":27,"style":27},"class Multiplier:\n    def __init__(self, factor):\n        self.factor = factor\n\n    def __call__(self, x):\n        return x * self.factor\n\ndouble = Multiplier(2)\nprint(double(5))         # 10 — double(...) is sugar for double.__call__(5)\nprint(callable(double))    # True\nprint(callable(5))           # False — plain objects aren't callable by default\n",[29,3455,3456,3465,3474,3486,3490,3500,3514,3518,3532,3547,3561],{"__ignoreMap":27},[32,3457,3458,3460,3463],{"class":34,"line":35},[32,3459,100],{"class":70},[32,3461,3462],{"class":103}," Multiplier",[32,3464,107],{"class":74},[32,3466,3467,3469,3471],{"class":34,"line":42},[32,3468,158],{"class":70},[32,3470,161],{"class":125},[32,3472,3473],{"class":74},"(self, factor):\n",[32,3475,3476,3478,3481,3483],{"class":34,"line":48},[32,3477,181],{"class":125},[32,3479,3480],{"class":74},".factor ",[32,3482,187],{"class":70},[32,3484,3485],{"class":74}," factor\n",[32,3487,3488],{"class":34,"line":54},[32,3489,64],{"emptyLinePlaceholder":63},[32,3491,3492,3494,3497],{"class":34,"line":60},[32,3493,158],{"class":70},[32,3495,3496],{"class":125}," __call__",[32,3498,3499],{"class":74},"(self, x):\n",[32,3501,3502,3504,3507,3509,3511],{"class":34,"line":67},[32,3503,247],{"class":70},[32,3505,3506],{"class":74}," x ",[32,3508,753],{"class":70},[32,3510,371],{"class":125},[32,3512,3513],{"class":74},".factor\n",[32,3515,3516],{"class":34,"line":78},[32,3517,64],{"emptyLinePlaceholder":63},[32,3519,3520,3523,3525,3528,3530],{"class":34,"line":92},[32,3521,3522],{"class":74},"double ",[32,3524,187],{"class":70},[32,3526,3527],{"class":74}," Multiplier(",[32,3529,1062],{"class":125},[32,3531,1044],{"class":74},[32,3533,3534,3536,3539,3541,3544],{"class":34,"line":97},[32,3535,1075],{"class":125},[32,3537,3538],{"class":74},"(double(",[32,3540,2378],{"class":125},[32,3542,3543],{"class":74},"))         ",[32,3545,3546],{"class":38},"# 10 — double(...) is sugar for double.__call__(5)\n",[32,3548,3549,3551,3553,3556,3559],{"class":34,"line":110},[32,3550,1075],{"class":125},[32,3552,921],{"class":74},[32,3554,3555],{"class":125},"callable",[32,3557,3558],{"class":74},"(double))    ",[32,3560,1865],{"class":38},[32,3562,3563,3565,3567,3569,3571,3573,3576],{"class":34,"line":117},[32,3564,1075],{"class":125},[32,3566,921],{"class":74},[32,3568,3555],{"class":125},[32,3570,921],{"class":74},[32,3572,2378],{"class":125},[32,3574,3575],{"class":74},"))           ",[32,3577,3578],{"class":38},"# False — plain objects aren't callable by default\n",[1519,3580,3581,3582,3584],{},"This is the mechanism behind class-based decorators (chapter 11) and any \"configured function object\" pattern — anywhere a plain function is expected, an instance with ",[29,3583,3447],{}," works identically.",[14,3586,3588],{"id":3587},"tips-tricks","💡 Tips & Tricks",[3590,3591,3592,3616,3637,3667,3693],"ul",{},[3593,3594,3595,3598,3599,3601,3602,3605,3606,3609,3610,1919,3612,3615],"li",{},[1526,3596,3597],{},"Idiom",": return ",[29,3600,2834],{}," (not ",[29,3603,3604],{},"False"," or raising ",[29,3607,3608],{},"TypeError",") from ",[29,3611,1294],{},[29,3613,3614],{},"__lt__","\u002Fetc. when comparing against an incompatible type — this lets Python fall back to trying the other operand's reflected method instead of prematurely deciding the objects aren't equal.",[3593,3617,3618,3621,3622,3625,3626,3629,3630,3633,3634,3636],{},[1526,3619,3620],{},"Debug",": ",[29,3623,3624],{},"object.__repr__"," (the default, unhelpful ",[29,3627,3628],{},"\u003CClassName object at 0x...>",") is what you get whenever ",[29,3631,3632],{},"__repr__"," is missing — seeing that format in a traceback or log is an immediate signal to add a ",[29,3635,3632],{}," to that class.",[3593,3638,3639,3621,3641,3644,3645,138,3648,138,3651,3654,3655,3657,3658,1919,3660,1919,3662,1919,3664,3666],{},[1526,3640,3597],{},[29,3642,3643],{},"functools.total_ordering"," fills in ",[29,3646,3647],{},"__le__",[29,3649,3650],{},"__gt__",[29,3652,3653],{},"__ge__"," automatically once you define ",[29,3656,1294],{}," and just one of ",[29,3659,3614],{},[29,3661,3647],{},[29,3663,3650],{},[29,3665,3653],{}," — saves writing four nearly-identical comparison methods by hand.",[3593,3668,3669,3621,3672,3675,3676,1919,3678,3680,3681,3683,3684,3687,3688,1919,3690,3692],{},[1526,3670,3671],{},"Performance",[29,3673,3674],{},"__slots__"," (chapter 15) and ",[29,3677,1294],{},[29,3679,1298],{}," interact subtly — a ",[29,3682,3674],{}," class with no ",[29,3685,3686],{},"__dict__"," still supports ",[29,3689,1294],{},[29,3691,1298],{}," normally, since those are defined on the class, not stored per-instance.",[3593,3694,3695,3621,3697,3700,3701,1919,3703,3705],{},[1526,3696,3620],{},[29,3698,3699],{},"contextlib.contextmanager"," (chapter 19) lets you write a context manager as a single generator function instead of a class with ",[29,3702,2858],{},[29,3704,2861],{}," — reach for it when the class-based ceremony feels like overkill for simple setup\u002Fteardown.",[14,3707,3709],{"id":3708},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3590,3711,3712,3743,3758,3788,3807],{},[3593,3713,3714,3722,3723,3726,3727,3729,3730,3732,3733,1919,3736,3739,3740,3742],{},[1526,3715,1521,3716,3718,3719,3721],{},[29,3717,1294],{}," without ",[29,3720,1298],{}," makes instances unhashable"," — Python automatically sets ",[29,3724,3725],{},"__hash__ = None"," on any class that defines ",[29,3728,1294],{}," but not ",[29,3731,1298],{},", because the default identity-based hash would violate the \"equal objects must hash equally\" contract; this silently breaks ",[29,3734,3735],{},"set",[29,3737,3738],{},"dict"," usage of that class until ",[29,3741,1298],{}," is explicitly defined too.",[3593,3744,3745,3748,3749,1919,3751,3754,3755,3757],{},[1526,3746,3747],{},"Hashing a mutable object on fields that later change corrupts its position in any set\u002Fdict it's already stored in"," — the object remains physically present but becomes unfindable via ",[29,3750,1692],{},[29,3752,3753],{},"[]"," because its current hash no longer matches the bucket it was inserted under; never make ",[29,3756,1298],{}," depend on fields that can mutate after insertion.",[3593,3759,3760,3769,3770,3773,3774,3777,3778,3781,3782,138,3785,3787],{},[1526,3761,3762,3763,3765,3766,3768],{},"Returning a truthy value from ",[29,3764,2861],{}," suppresses ANY exception raised in the ",[29,3767,2995],{}," block, not just ones you intended to catch"," — a careless ",[29,3771,3772],{},"return True"," (instead of only returning ",[29,3775,3776],{},"True"," after checking ",[29,3779,3780],{},"exc_type",") silently swallows every exception, including ones signaling real bugs (",[29,3783,3784],{},"KeyError",[29,3786,3608],{}," from a typo) that should have propagated.",[3593,3789,3790,3800,3801,3803,3804,3806],{},[1526,3791,3792,1919,3794,3796,3797,3799],{},[29,3793,2819],{},[29,3795,2815],{},"\u002Fetc. are only tried if the left operand's corresponding method returns ",[29,3798,2834],{}," or doesn't exist — not simply \"whenever the right operand also defines it\""," — if the left operand's ",[29,3802,677],{}," happens to accept anything (e.g., via duck typing) and doesn't raise, ",[29,3805,2819],{}," on the right operand never gets a chance to run.",[3593,3808,3809,3824,3825,138,3827,138,3829,3831,3832,3834,3835,3837],{},[1526,3810,3811,3812,3814,3815,3817,3818,138,3820,2213,3822],{},"A class implementing only ",[29,3813,1932],{}," (no ",[29,3816,2220],{},") supports iteration via a legacy fallback that calls ",[29,3819,2209],{},[29,3821,2212],{},[29,3823,2216],{}," — this silently breaks for objects using non-integer or non-sequential keys (like a dict-backed mapping), where indexing by ",[29,3826,1167],{},[29,3828,1057],{},[29,3830,1062],{},"... raises ",[29,3833,3784],{}," instead of the ",[29,3836,2216],{}," the fallback protocol specifically expects, crashing iteration instead of stopping cleanly.",[14,3839,3841],{"id":3840},"spot-the-bug","🧠 Spot the Bug",[1519,3843,3844,3845,3848],{},"A caching layer stores computed results keyed by a custom ",[29,3846,3847],{},"CacheKey"," object. Cache lookups intermittently fail to find entries that were definitely inserted. Find the bug.",[19,3850,3851],{"language":21},[23,3852,3854],{"className":25,"code":3853,"language":21,"meta":27,"style":27},"class CacheKey:\n    def __init__(self, params):\n        self.params = params      # a dict of parameters\n\n    def __eq__(self, other):\n        return self.params == other.params\n\n    def __hash__(self):\n        return hash(tuple(sorted(self.params.items())))\n\ncache = {}\nkey = CacheKey({\"user_id\": 42, \"page\": 1})\ncache[key] = \"cached result\"\n\nkey.params[\"page\"] = 2      # params mutated after being used as a cache key\nprint(cache.get(CacheKey({\"user_id\": 42, \"page\": 2})))\nprint(len(cache))\n",[29,3855,3856,3865,3874,3889,3893,3901,3914,3918,3926,3948,3952,3962,3992,4002,4006,4023,4047],{"__ignoreMap":27},[32,3857,3858,3860,3863],{"class":34,"line":35},[32,3859,100],{"class":70},[32,3861,3862],{"class":103}," CacheKey",[32,3864,107],{"class":74},[32,3866,3867,3869,3871],{"class":34,"line":42},[32,3868,158],{"class":70},[32,3870,161],{"class":125},[32,3872,3873],{"class":74},"(self, params):\n",[32,3875,3876,3878,3881,3883,3886],{"class":34,"line":48},[32,3877,181],{"class":125},[32,3879,3880],{"class":74},".params ",[32,3882,187],{"class":70},[32,3884,3885],{"class":74}," params      ",[32,3887,3888],{"class":38},"# a dict of parameters\n",[32,3890,3891],{"class":34,"line":54},[32,3892,64],{"emptyLinePlaceholder":63},[32,3894,3895,3897,3899],{"class":34,"line":60},[32,3896,158],{"class":70},[32,3898,429],{"class":125},[32,3900,1392],{"class":74},[32,3902,3903,3905,3907,3909,3911],{"class":34,"line":67},[32,3904,247],{"class":70},[32,3906,371],{"class":125},[32,3908,3880],{"class":74},[32,3910,482],{"class":70},[32,3912,3913],{"class":74}," other.params\n",[32,3915,3916],{"class":34,"line":78},[32,3917,64],{"emptyLinePlaceholder":63},[32,3919,3920,3922,3924],{"class":34,"line":92},[32,3921,158],{"class":70},[32,3923,510],{"class":125},[32,3925,957],{"class":74},[32,3927,3928,3930,3932,3934,3936,3938,3941,3943,3945],{"class":34,"line":97},[32,3929,247],{"class":70},[32,3931,537],{"class":125},[32,3933,921],{"class":74},[32,3935,1805],{"class":125},[32,3937,921],{"class":74},[32,3939,3940],{"class":125},"sorted",[32,3942,921],{"class":74},[32,3944,543],{"class":125},[32,3946,3947],{"class":74},".params.items())))\n",[32,3949,3950],{"class":34,"line":110},[32,3951,64],{"emptyLinePlaceholder":63},[32,3953,3954,3957,3959],{"class":34,"line":117},[32,3955,3956],{"class":74},"cache ",[32,3958,187],{"class":70},[32,3960,3961],{"class":74}," {}\n",[32,3963,3964,3967,3969,3972,3975,3977,3980,3982,3985,3987,3989],{"class":34,"line":122},[32,3965,3966],{"class":74},"key ",[32,3968,187],{"class":70},[32,3970,3971],{"class":74}," CacheKey({",[32,3973,3974],{"class":113},"\"user_id\"",[32,3976,3621],{"class":74},[32,3978,3979],{"class":125},"42",[32,3981,138],{"class":74},[32,3983,3984],{"class":113},"\"page\"",[32,3986,3621],{"class":74},[32,3988,1057],{"class":125},[32,3990,3991],{"class":74},"})\n",[32,3993,3994,3997,3999],{"class":34,"line":150},[32,3995,3996],{"class":74},"cache[key] ",[32,3998,187],{"class":70},[32,4000,4001],{"class":113}," \"cached result\"\n",[32,4003,4004],{"class":34,"line":155},[32,4005,64],{"emptyLinePlaceholder":63},[32,4007,4008,4011,4013,4016,4018,4020],{"class":34,"line":178},[32,4009,4010],{"class":74},"key.params[",[32,4012,3984],{"class":113},[32,4014,4015],{"class":74},"] ",[32,4017,187],{"class":70},[32,4019,1009],{"class":125},[32,4021,4022],{"class":38},"      # params mutated after being used as a cache key\n",[32,4024,4025,4027,4030,4032,4034,4036,4038,4040,4042,4044],{"class":34,"line":196},[32,4026,1075],{"class":125},[32,4028,4029],{"class":74},"(cache.get(CacheKey({",[32,4031,3974],{"class":113},[32,4033,3621],{"class":74},[32,4035,3979],{"class":125},[32,4037,138],{"class":74},[32,4039,3984],{"class":113},[32,4041,3621],{"class":74},[32,4043,1062],{"class":125},[32,4045,4046],{"class":74},"})))\n",[32,4048,4049,4051,4053,4055],{"class":34,"line":211},[32,4050,1075],{"class":125},[32,4052,921],{"class":74},[32,4054,2092],{"class":125},[32,4056,4057],{"class":74},"(cache))\n",[4059,4060,4061,4065,4123,4133,4139,4242,4262],"details",{},[4062,4063,4064],"summary",{},"Answer",[1519,4066,4067,4068,4070,4071,4073,4074,4077,4078,4081,4082,4085,4086,138,4089,4092,4093,4096,4097,4099,4100,4104,4105,4108,4109,4112,4113,4116,4117,4119,4120,4122],{},"Prints ",[29,4069,1537],{}," (lookup miss) and ",[29,4072,1057],{}," (the entry is still there, just unreachable). ",[29,4075,4076],{},"CacheKey.__hash__"," is computed from ",[29,4079,4080],{},"self.params",", which is a ",[1526,4083,4084],{},"mutable dict"," — after ",[29,4087,4088],{},"key.params[\"page\"] = 2",[29,4090,4091],{},"key","'s hash value changes. But the dict ",[29,4094,4095],{},"cache"," already placed ",[29,4098,4091],{}," into a bucket based on its ",[4101,4102,4103],"em",{},"original"," hash (with ",[29,4106,4107],{},"page: 1","). Python's dict now looks for a lookup key with ",[29,4110,4111],{},"page: 2"," in a ",[4101,4114,4115],{},"different"," bucket than where the mutated ",[29,4118,4091],{}," actually lives, so it finds nothing — even though, logically, ",[29,4121,4091],{}," now has exactly the params being searched for.",[1519,4124,4125,4126,4128,4129,4132],{},"The underlying object is still in ",[29,4127,4095],{}," (confirmed by ",[29,4130,4131],{},"len(cache) == 1","), just permanently unreachable via normal lookup, since its current hash doesn't match the bucket it occupies.",[1519,4134,4135,4136,4138],{},"The fix is to never allow the fields that participate in ",[29,4137,1298],{}," to mutate after use as a dict\u002Fset key — use an immutable snapshot instead:",[19,4140,4141],{"language":21},[23,4142,4144],{"className":25,"code":4143,"language":21,"meta":27,"style":27},"class CacheKey:\n    def __init__(self, params):\n        self.params = dict(params)     # still technically mutable, but treat as frozen after this point\n\n    def __eq__(self, other):\n        return isinstance(other, CacheKey) and self.params == other.params\n\n    def __hash__(self):\n        return hash(tuple(sorted(self.params.items())))\n",[29,4145,4146,4154,4162,4179,4183,4191,4210,4214,4222],{"__ignoreMap":27},[32,4147,4148,4150,4152],{"class":34,"line":35},[32,4149,100],{"class":70},[32,4151,3862],{"class":103},[32,4153,107],{"class":74},[32,4155,4156,4158,4160],{"class":34,"line":42},[32,4157,158],{"class":70},[32,4159,161],{"class":125},[32,4161,3873],{"class":74},[32,4163,4164,4166,4168,4170,4173,4176],{"class":34,"line":48},[32,4165,181],{"class":125},[32,4167,3880],{"class":74},[32,4169,187],{"class":70},[32,4171,4172],{"class":125}," dict",[32,4174,4175],{"class":74},"(params)     ",[32,4177,4178],{"class":38},"# still technically mutable, but treat as frozen after this point\n",[32,4180,4181],{"class":34,"line":54},[32,4182,64],{"emptyLinePlaceholder":63},[32,4184,4185,4187,4189],{"class":34,"line":60},[32,4186,158],{"class":70},[32,4188,429],{"class":125},[32,4190,1392],{"class":74},[32,4192,4193,4195,4197,4200,4202,4204,4206,4208],{"class":34,"line":67},[32,4194,247],{"class":70},[32,4196,455],{"class":125},[32,4198,4199],{"class":74},"(other, CacheKey) ",[32,4201,488],{"class":70},[32,4203,371],{"class":125},[32,4205,3880],{"class":74},[32,4207,482],{"class":70},[32,4209,3913],{"class":74},[32,4211,4212],{"class":34,"line":78},[32,4213,64],{"emptyLinePlaceholder":63},[32,4215,4216,4218,4220],{"class":34,"line":92},[32,4217,158],{"class":70},[32,4219,510],{"class":125},[32,4221,957],{"class":74},[32,4223,4224,4226,4228,4230,4232,4234,4236,4238,4240],{"class":34,"line":97},[32,4225,247],{"class":70},[32,4227,537],{"class":125},[32,4229,921],{"class":74},[32,4231,1805],{"class":125},[32,4233,921],{"class":74},[32,4235,3940],{"class":125},[32,4237,921],{"class":74},[32,4239,543],{"class":125},[32,4241,3947],{"class":74},[1519,4243,4244,4245,1919,4247,4249,4250,4253,4254,4257,4258,4261],{},"The real discipline is procedural: once an object has been hashed and inserted somewhere, never mutate the fields ",[29,4246,1298],{},[29,4248,1294],{}," depend on — or use a genuinely immutable structure (a ",[29,4251,4252],{},"frozenset"," of items, a ",[29,4255,4256],{},"namedtuple",", or a frozen ",[29,4259,4260],{},"dataclass",") so mutation is impossible by construction.",[1519,4263,4264,4267],{},[1526,4265,4266],{},"The lesson",": hashability and mutability are fundamentally in tension — an object used as a dict key or set member must be treated as frozen for as long as it's stored there, or lookups silently and permanently fail without any error.",[14,4269,4271],{"id":4270},"key-takeaways","Key Takeaways",[3590,4273,4274,4286,4298,4310,4327,4337],{},[3593,4275,4276,4278,4279,4282,4283,4285],{},[29,4277,3632],{}," is for developers (should be unambiguous, ideally eval-able) and is always used by containers; ",[29,4280,4281],{},"__str__"," is for end users and falls back to ",[29,4284,3632],{}," if absent.",[3593,4287,4288,1295,4290,4292,4293,3718,4295,4297],{},[29,4289,1294],{},[29,4291,1298],{}," must stay consistent — equal objects must hash equally — and Python disables hashing automatically if you define ",[29,4294,1294],{},[29,4296,1298],{},"; never hash on fields that can mutate after the object is stored in a set\u002Fdict.",[3593,4299,4300,4301,4303,4304,4306,4307,4309],{},"Implementing ",[29,4302,1932],{}," alone gives you slicing, ",[29,4305,1692],{},", and a legacy iteration fallback for free; implementing ",[29,4308,2220],{}," (often as a generator function) is the more robust, explicit way to make a class iterable and reusable across multiple loops.",[3593,4311,4312,4313,138,4315,4317,4318,4320,4321,4323,4324,4326],{},"Operator overloading (",[29,4314,677],{},[29,4316,800],{},", etc.) should return ",[29,4319,2834],{},", not raise or return ",[29,4322,3604],{},", when given an incompatible type — this lets Python try the reflected method (",[29,4325,2819],{},") on the other operand.",[3593,4328,4329,1919,4331,4333,4334,4336],{},[29,4330,2858],{},[29,4332,2861],{}," implement the context manager protocol; ",[29,4335,2861],{}," always runs (even on exceptions) and can suppress an exception by returning a truthy value — do this narrowly and deliberately, never as a blanket catch-all.",[3593,4338,4339,4341],{},[29,4340,3447],{}," makes instances directly callable like functions, underlying class-based decorators and configurable callback objects.",[4343,4344,4345],"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 .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}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":27,"searchDepth":42,"depth":42,"links":4347},[4348,4349,4353,4355,4357,4358,4362,4364,4365,4366,4367],{"id":16,"depth":42,"text":17},{"id":1290,"depth":42,"text":4350,"children":4351},"Equality and Hashing: __eq__ and __hash__",[4352],{"id":1716,"depth":48,"text":1717},{"id":1925,"depth":42,"text":4354},"Container Protocols: __len__, __getitem__, __contains__",{"id":2227,"depth":42,"text":4356},"Making an Object Iterable: __iter__",{"id":2424,"depth":42,"text":2425},{"id":2854,"depth":42,"text":4359,"children":4360},"Context Managers: __enter__ and __exit__",[4361],{"id":3233,"depth":48,"text":3234},{"id":3444,"depth":42,"text":4363},"__call__ — Making Instances Callable",{"id":3587,"depth":42,"text":3588},{"id":3708,"depth":42,"text":3709},{"id":3840,"depth":42,"text":3841},{"id":4270,"depth":42,"text":4271},"md",{},"\u002Fpython\u002F14-magic-methods-and-protocols",{"title":5,"description":27},"python\u002F14-magic-methods-and-protocols","6NMxAlaH9mVv6IM80K_uLU-rckUruqO6VumQ7OjPcEM",1789924651573]