[{"data":1,"prerenderedAt":2975},["ShallowReactive",2],{"page-\u002Fpython\u002F19-iterators-and-context-managers":3},{"id":4,"title":5,"body":6,"description":60,"extension":2969,"meta":2970,"navigation":120,"path":2971,"seo":2972,"stem":2973,"__hash__":2974},"content\u002Fpython\u002F19-iterators-and-context-managers.md","19 — Iterators & Context Managers Deep Dive",{"type":7,"value":8,"toc":2942},"minimark",[9,13,18,51,359,377,384,496,534,538,726,743,750,929,952,960,1078,1082,1202,1230,1237,1375,1394,1505,1512,1605,1626,1633,1639,1986,1994,2061,2074,2082,2092,2242,2255,2259,2358,2362,2468,2472,2475,2605,2854,2858,2938],[10,11,5],"h1",{"id":12},"_19-iterators-context-managers-deep-dive",[14,15,17],"h2",{"id":16},"the-iterator-protocol-precisely","The Iterator Protocol, Precisely",[19,20,21,22,26,27,31,32,35,36,39,40,42,43,46,47,50],"p",{},"Two related but distinct protocols power every ",[23,24,25],"code",{},"for"," loop in Python: ",[28,29,30],"strong",{},"iterable"," (has ",[23,33,34],{},"__iter__",", returns an iterator) and ",[28,37,38],{},"iterator"," (has both ",[23,41,34],{}," — returning itself — and ",[23,44,45],{},"__next__",", which produces values or raises ",[23,48,49],{},"StopIteration",").",[52,53,55],"code-wrapper",{"language":54},"python",[56,57,61],"pre",{"className":58,"code":59,"language":54,"meta":60,"style":60},"language-python shiki shiki-themes github-light github-dark","class CountUp:\n    \"\"\"An ITERABLE — __iter__ returns a fresh iterator each time.\"\"\"\n    def __init__(self, limit):\n        self.limit = limit\n\n    def __iter__(self):\n        return CountUpIterator(self.limit)\n\nclass CountUpIterator:\n    \"\"\"An ITERATOR — __next__ produces values, __iter__ returns self.\"\"\"\n    def __init__(self, limit):\n        self.limit = limit\n        self.current = 0\n\n    def __iter__(self):\n        return self          # an iterator is its own iterable\n\n    def __next__(self):\n        if self.current >= self.limit:\n            raise StopIteration\n        value = self.current\n        self.current += 1\n        return value\n\ncounter = CountUp(3)\nprint(list(counter))   # [0, 1, 2]\nprint(list(counter))     # [0, 1, 2] — REUSABLE, because __iter__ creates a NEW iterator each time\n","",[23,62,63,80,87,100,115,122,133,148,153,163,169,178,189,202,207,216,228,233,243,261,270,283,296,304,309,326,344],{"__ignoreMap":60},[64,65,68,72,76],"span",{"class":66,"line":67},"line",1,[64,69,71],{"class":70},"svdQ7","class",[64,73,75],{"class":74},"sIsaT"," CountUp",[64,77,79],{"class":78},"ssxIu",":\n",[64,81,83],{"class":66,"line":82},2,[64,84,86],{"class":85},"sJ6F3","    \"\"\"An ITERABLE — __iter__ returns a fresh iterator each time.\"\"\"\n",[64,88,90,93,97],{"class":66,"line":89},3,[64,91,92],{"class":70},"    def",[64,94,96],{"class":95},"snvgF"," __init__",[64,98,99],{"class":78},"(self, limit):\n",[64,101,103,106,109,112],{"class":66,"line":102},4,[64,104,105],{"class":95},"        self",[64,107,108],{"class":78},".limit ",[64,110,111],{"class":70},"=",[64,113,114],{"class":78}," limit\n",[64,116,118],{"class":66,"line":117},5,[64,119,121],{"emptyLinePlaceholder":120},true,"\n",[64,123,125,127,130],{"class":66,"line":124},6,[64,126,92],{"class":70},[64,128,129],{"class":95}," __iter__",[64,131,132],{"class":78},"(self):\n",[64,134,136,139,142,145],{"class":66,"line":135},7,[64,137,138],{"class":70},"        return",[64,140,141],{"class":78}," CountUpIterator(",[64,143,144],{"class":95},"self",[64,146,147],{"class":78},".limit)\n",[64,149,151],{"class":66,"line":150},8,[64,152,121],{"emptyLinePlaceholder":120},[64,154,156,158,161],{"class":66,"line":155},9,[64,157,71],{"class":70},[64,159,160],{"class":74}," CountUpIterator",[64,162,79],{"class":78},[64,164,166],{"class":66,"line":165},10,[64,167,168],{"class":85},"    \"\"\"An ITERATOR — __next__ produces values, __iter__ returns self.\"\"\"\n",[64,170,172,174,176],{"class":66,"line":171},11,[64,173,92],{"class":70},[64,175,96],{"class":95},[64,177,99],{"class":78},[64,179,181,183,185,187],{"class":66,"line":180},12,[64,182,105],{"class":95},[64,184,108],{"class":78},[64,186,111],{"class":70},[64,188,114],{"class":78},[64,190,192,194,197,199],{"class":66,"line":191},13,[64,193,105],{"class":95},[64,195,196],{"class":78},".current ",[64,198,111],{"class":70},[64,200,201],{"class":95}," 0\n",[64,203,205],{"class":66,"line":204},14,[64,206,121],{"emptyLinePlaceholder":120},[64,208,210,212,214],{"class":66,"line":209},15,[64,211,92],{"class":70},[64,213,129],{"class":95},[64,215,132],{"class":78},[64,217,219,221,224],{"class":66,"line":218},16,[64,220,138],{"class":70},[64,222,223],{"class":95}," self",[64,225,227],{"class":226},"sdCPZ","          # an iterator is its own iterable\n",[64,229,231],{"class":66,"line":230},17,[64,232,121],{"emptyLinePlaceholder":120},[64,234,236,238,241],{"class":66,"line":235},18,[64,237,92],{"class":70},[64,239,240],{"class":95}," __next__",[64,242,132],{"class":78},[64,244,246,249,251,253,256,258],{"class":66,"line":245},19,[64,247,248],{"class":70},"        if",[64,250,223],{"class":95},[64,252,196],{"class":78},[64,254,255],{"class":70},">=",[64,257,223],{"class":95},[64,259,260],{"class":78},".limit:\n",[64,262,264,267],{"class":66,"line":263},20,[64,265,266],{"class":70},"            raise",[64,268,269],{"class":95}," StopIteration\n",[64,271,273,276,278,280],{"class":66,"line":272},21,[64,274,275],{"class":78},"        value ",[64,277,111],{"class":70},[64,279,223],{"class":95},[64,281,282],{"class":78},".current\n",[64,284,286,288,290,293],{"class":66,"line":285},22,[64,287,105],{"class":95},[64,289,196],{"class":78},[64,291,292],{"class":70},"+=",[64,294,295],{"class":95}," 1\n",[64,297,299,301],{"class":66,"line":298},23,[64,300,138],{"class":70},[64,302,303],{"class":78}," value\n",[64,305,307],{"class":66,"line":306},24,[64,308,121],{"emptyLinePlaceholder":120},[64,310,312,315,317,320,323],{"class":66,"line":311},25,[64,313,314],{"class":78},"counter ",[64,316,111],{"class":70},[64,318,319],{"class":78}," CountUp(",[64,321,322],{"class":95},"3",[64,324,325],{"class":78},")\n",[64,327,329,332,335,338,341],{"class":66,"line":328},26,[64,330,331],{"class":95},"print",[64,333,334],{"class":78},"(",[64,336,337],{"class":95},"list",[64,339,340],{"class":78},"(counter))   ",[64,342,343],{"class":226},"# [0, 1, 2]\n",[64,345,347,349,351,353,356],{"class":66,"line":346},27,[64,348,331],{"class":95},[64,350,334],{"class":78},[64,352,337],{"class":95},[64,354,355],{"class":78},"(counter))     ",[64,357,358],{"class":226},"# [0, 1, 2] — REUSABLE, because __iter__ creates a NEW iterator each time\n",[19,360,361,362,365,366,368,369,372,373,376],{},"This is precisely why ",[23,363,364],{},"CountUp"," (an iterable factory) is reusable across multiple ",[23,367,25],{}," loops, while a raw generator object (chapter 09) is not — the generator object IS its own iterator (single-use), whereas ",[23,370,371],{},"CountUp.__iter__()"," manufactures a brand-new ",[23,374,375],{},"CountUpIterator"," instance on every call.",[14,378,380,381,383],{"id":379},"what-for-actually-does","What ",[23,382,25],{}," Actually Does",[52,385,386],{"language":54},[56,387,389],{"className":58,"code":388,"language":54,"meta":60,"style":60},"# This for loop:\nfor item in some_iterable:\n    print(item)\n\n# is exactly equivalent to:\n_iterator = iter(some_iterable)      # calls some_iterable.__iter__()\nwhile True:\n    try:\n        item = next(_iterator)         # calls _iterator.__next__()\n    except StopIteration:\n        break\n    print(item)\n",[23,390,391,396,409,417,421,426,442,452,459,475,485,490],{"__ignoreMap":60},[64,392,393],{"class":66,"line":67},[64,394,395],{"class":226},"# This for loop:\n",[64,397,398,400,403,406],{"class":66,"line":82},[64,399,25],{"class":70},[64,401,402],{"class":78}," item ",[64,404,405],{"class":70},"in",[64,407,408],{"class":78}," some_iterable:\n",[64,410,411,414],{"class":66,"line":89},[64,412,413],{"class":95},"    print",[64,415,416],{"class":78},"(item)\n",[64,418,419],{"class":66,"line":102},[64,420,121],{"emptyLinePlaceholder":120},[64,422,423],{"class":66,"line":117},[64,424,425],{"class":226},"# is exactly equivalent to:\n",[64,427,428,431,433,436,439],{"class":66,"line":124},[64,429,430],{"class":78},"_iterator ",[64,432,111],{"class":70},[64,434,435],{"class":95}," iter",[64,437,438],{"class":78},"(some_iterable)      ",[64,440,441],{"class":226},"# calls some_iterable.__iter__()\n",[64,443,444,447,450],{"class":66,"line":135},[64,445,446],{"class":70},"while",[64,448,449],{"class":95}," True",[64,451,79],{"class":78},[64,453,454,457],{"class":66,"line":150},[64,455,456],{"class":70},"    try",[64,458,79],{"class":78},[64,460,461,464,466,469,472],{"class":66,"line":155},[64,462,463],{"class":78},"        item ",[64,465,111],{"class":70},[64,467,468],{"class":95}," next",[64,470,471],{"class":78},"(_iterator)         ",[64,473,474],{"class":226},"# calls _iterator.__next__()\n",[64,476,477,480,483],{"class":66,"line":165},[64,478,479],{"class":70},"    except",[64,481,482],{"class":95}," StopIteration",[64,484,79],{"class":78},[64,486,487],{"class":66,"line":171},[64,488,489],{"class":70},"        break\n",[64,491,492,494],{"class":66,"line":180},[64,493,413],{"class":95},[64,495,416],{"class":78},[19,497,498,501,502,505,506,508,509,511,512,514,515,508,517,508,520,523,524,526,527,530,531,533],{},[23,499,500],{},"iter()"," and ",[23,503,504],{},"next()"," are the built-in functions that invoke ",[23,507,34],{},"\u002F",[23,510,45],{}," — understanding a ",[23,513,25],{}," loop as sugar for this exact ",[23,516,446],{},[23,518,519],{},"try",[23,521,522],{},"except StopIteration"," loop demystifies why generators (which raise ",[23,525,49],{}," automatically when they ",[23,528,529],{},"return",") slot into ",[23,532,25],{}," loops seamlessly.",[14,535,537],{"id":536},"generators-satisfy-the-iterator-protocol-automatically","Generators Satisfy the Iterator Protocol Automatically",[52,539,540],{"language":54},[56,541,543],{"className":58,"code":542,"language":54,"meta":60,"style":60},"def count_up(limit):\n    current = 0\n    while current \u003C limit:\n        yield current\n        current += 1\n\ngen = count_up(3)\nprint(hasattr(gen, \"__iter__\"))    # True\nprint(hasattr(gen, \"__next__\"))      # True\nprint(iter(gen) is gen)                # True — a generator is its own iterator\n\nprint(next(gen))   # 0\nprint(next(gen))     # 1\nprint(next(gen))       # 2\n# next(gen)             # StopIteration\n",[23,544,545,556,565,579,587,596,600,614,635,653,674,678,693,707,721],{"__ignoreMap":60},[64,546,547,550,553],{"class":66,"line":67},[64,548,549],{"class":70},"def",[64,551,552],{"class":74}," count_up",[64,554,555],{"class":78},"(limit):\n",[64,557,558,561,563],{"class":66,"line":82},[64,559,560],{"class":78},"    current ",[64,562,111],{"class":70},[64,564,201],{"class":95},[64,566,567,570,573,576],{"class":66,"line":89},[64,568,569],{"class":70},"    while",[64,571,572],{"class":78}," current ",[64,574,575],{"class":70},"\u003C",[64,577,578],{"class":78}," limit:\n",[64,580,581,584],{"class":66,"line":102},[64,582,583],{"class":70},"        yield",[64,585,586],{"class":78}," current\n",[64,588,589,592,594],{"class":66,"line":117},[64,590,591],{"class":78},"        current ",[64,593,292],{"class":70},[64,595,295],{"class":95},[64,597,598],{"class":66,"line":124},[64,599,121],{"emptyLinePlaceholder":120},[64,601,602,605,607,610,612],{"class":66,"line":135},[64,603,604],{"class":78},"gen ",[64,606,111],{"class":70},[64,608,609],{"class":78}," count_up(",[64,611,322],{"class":95},[64,613,325],{"class":78},[64,615,616,618,620,623,626,629,632],{"class":66,"line":150},[64,617,331],{"class":95},[64,619,334],{"class":78},[64,621,622],{"class":95},"hasattr",[64,624,625],{"class":78},"(gen, ",[64,627,628],{"class":85},"\"__iter__\"",[64,630,631],{"class":78},"))    ",[64,633,634],{"class":226},"# True\n",[64,636,637,639,641,643,645,648,651],{"class":66,"line":155},[64,638,331],{"class":95},[64,640,334],{"class":78},[64,642,622],{"class":95},[64,644,625],{"class":78},[64,646,647],{"class":85},"\"__next__\"",[64,649,650],{"class":78},"))      ",[64,652,634],{"class":226},[64,654,655,657,659,662,665,668,671],{"class":66,"line":165},[64,656,331],{"class":95},[64,658,334],{"class":78},[64,660,661],{"class":95},"iter",[64,663,664],{"class":78},"(gen) ",[64,666,667],{"class":70},"is",[64,669,670],{"class":78}," gen)                ",[64,672,673],{"class":226},"# True — a generator is its own iterator\n",[64,675,676],{"class":66,"line":171},[64,677,121],{"emptyLinePlaceholder":120},[64,679,680,682,684,687,690],{"class":66,"line":180},[64,681,331],{"class":95},[64,683,334],{"class":78},[64,685,686],{"class":95},"next",[64,688,689],{"class":78},"(gen))   ",[64,691,692],{"class":226},"# 0\n",[64,694,695,697,699,701,704],{"class":66,"line":191},[64,696,331],{"class":95},[64,698,334],{"class":78},[64,700,686],{"class":95},[64,702,703],{"class":78},"(gen))     ",[64,705,706],{"class":226},"# 1\n",[64,708,709,711,713,715,718],{"class":66,"line":204},[64,710,331],{"class":95},[64,712,334],{"class":78},[64,714,686],{"class":95},[64,716,717],{"class":78},"(gen))       ",[64,719,720],{"class":226},"# 2\n",[64,722,723],{"class":66,"line":209},[64,724,725],{"class":226},"# next(gen)             # StopIteration\n",[19,727,728,729,731,732,735,736,738,739,742],{},"This is why implementing ",[23,730,34],{}," as a generator function (",[23,733,734],{},"def __iter__(self): yield from self._items",") — shown in chapter 14 — is almost always simpler than hand-writing a separate iterator class with manual ",[23,737,45],{},"\u002Fstate tracking: the generator's local variables and ",[23,740,741],{},"yield"," statements ARE the state machine, generated for you by the interpreter.",[14,744,746,749],{"id":745},"contextlibcontextmanager-context-managers-without-a-class",[23,747,748],{},"contextlib.contextmanager"," — Context Managers Without a Class",[52,751,752],{"language":54},[56,753,755],{"className":58,"code":754,"language":54,"meta":60,"style":60},"from contextlib import contextmanager\nimport time\n\n@contextmanager\ndef timer(label):\n    start = time.perf_counter()\n    try:\n        yield              # code inside the `with` block runs here\n    finally:\n        elapsed = time.perf_counter() - start\n        print(f\"{label}: {elapsed:.4f}s\")\n\nwith timer(\"computation\"):\n    total = sum(range(10_000_000))\n# computation: 0.1234s\n",[23,756,757,771,778,782,787,797,807,813,820,827,843,883,887,901,924],{"__ignoreMap":60},[64,758,759,762,765,768],{"class":66,"line":67},[64,760,761],{"class":70},"from",[64,763,764],{"class":78}," contextlib ",[64,766,767],{"class":70},"import",[64,769,770],{"class":78}," contextmanager\n",[64,772,773,775],{"class":66,"line":82},[64,774,767],{"class":70},[64,776,777],{"class":78}," time\n",[64,779,780],{"class":66,"line":89},[64,781,121],{"emptyLinePlaceholder":120},[64,783,784],{"class":66,"line":102},[64,785,786],{"class":74},"@contextmanager\n",[64,788,789,791,794],{"class":66,"line":117},[64,790,549],{"class":70},[64,792,793],{"class":74}," timer",[64,795,796],{"class":78},"(label):\n",[64,798,799,802,804],{"class":66,"line":124},[64,800,801],{"class":78},"    start ",[64,803,111],{"class":70},[64,805,806],{"class":78}," time.perf_counter()\n",[64,808,809,811],{"class":66,"line":135},[64,810,456],{"class":70},[64,812,79],{"class":78},[64,814,815,817],{"class":66,"line":150},[64,816,583],{"class":70},[64,818,819],{"class":226},"              # code inside the `with` block runs here\n",[64,821,822,825],{"class":66,"line":155},[64,823,824],{"class":70},"    finally",[64,826,79],{"class":78},[64,828,829,832,834,837,840],{"class":66,"line":165},[64,830,831],{"class":78},"        elapsed ",[64,833,111],{"class":70},[64,835,836],{"class":78}," time.perf_counter() ",[64,838,839],{"class":70},"-",[64,841,842],{"class":78}," start\n",[64,844,845,848,850,853,856,859,862,865,868,870,873,876,878,881],{"class":66,"line":171},[64,846,847],{"class":95},"        print",[64,849,334],{"class":78},[64,851,852],{"class":70},"f",[64,854,855],{"class":85},"\"",[64,857,858],{"class":95},"{",[64,860,861],{"class":78},"label",[64,863,864],{"class":95},"}",[64,866,867],{"class":85},": ",[64,869,858],{"class":95},[64,871,872],{"class":78},"elapsed",[64,874,875],{"class":70},":.4f",[64,877,864],{"class":95},[64,879,880],{"class":85},"s\"",[64,882,325],{"class":78},[64,884,885],{"class":66,"line":180},[64,886,121],{"emptyLinePlaceholder":120},[64,888,889,892,895,898],{"class":66,"line":191},[64,890,891],{"class":70},"with",[64,893,894],{"class":78}," timer(",[64,896,897],{"class":85},"\"computation\"",[64,899,900],{"class":78},"):\n",[64,902,903,906,908,911,913,916,918,921],{"class":66,"line":204},[64,904,905],{"class":78},"    total ",[64,907,111],{"class":70},[64,909,910],{"class":95}," sum",[64,912,334],{"class":78},[64,914,915],{"class":95},"range",[64,917,334],{"class":78},[64,919,920],{"class":95},"10_000_000",[64,922,923],{"class":78},"))\n",[64,925,926],{"class":66,"line":209},[64,927,928],{"class":226},"# computation: 0.1234s\n",[19,930,931,932,934,935,938,939,942,943,946,947,508,949,951],{},"Everything before ",[23,933,741],{}," is the ",[23,936,937],{},"__enter__"," logic; everything after (in the ",[23,940,941],{},"finally",") is ",[23,944,945],{},"__exit__",". This single-generator-function style is generally preferred over writing a full ",[23,948,937],{},[23,950,945],{}," class for simple setup\u002Fteardown, exactly mirroring the \"generator vs iterator class\" simplification from the previous section.",[953,954,956,957],"h3",{"id":955},"yielding-a-value-for-as","Yielding a value for ",[23,958,959],{},"as",[52,961,962],{"language":54},[56,963,965],{"className":58,"code":964,"language":54,"meta":60,"style":60},"from contextlib import contextmanager\n\n@contextmanager\ndef open_upper(path):\n    f = open(path, encoding=\"utf-8\")\n    try:\n        yield f.read().upper()    # this becomes the value bound by `as`\n    finally:\n        f.close()\n\nwith open_upper(\"notes.txt\") as content:\n    print(content)   # file contents, uppercased\n",[23,966,967,977,981,985,995,1019,1025,1035,1041,1046,1050,1068],{"__ignoreMap":60},[64,968,969,971,973,975],{"class":66,"line":67},[64,970,761],{"class":70},[64,972,764],{"class":78},[64,974,767],{"class":70},[64,976,770],{"class":78},[64,978,979],{"class":66,"line":82},[64,980,121],{"emptyLinePlaceholder":120},[64,982,983],{"class":66,"line":89},[64,984,786],{"class":74},[64,986,987,989,992],{"class":66,"line":102},[64,988,549],{"class":70},[64,990,991],{"class":74}," open_upper",[64,993,994],{"class":78},"(path):\n",[64,996,997,1000,1002,1005,1008,1012,1014,1017],{"class":66,"line":117},[64,998,999],{"class":78},"    f ",[64,1001,111],{"class":70},[64,1003,1004],{"class":95}," open",[64,1006,1007],{"class":78},"(path, ",[64,1009,1011],{"class":1010},"sCrzJ","encoding",[64,1013,111],{"class":70},[64,1015,1016],{"class":85},"\"utf-8\"",[64,1018,325],{"class":78},[64,1020,1021,1023],{"class":66,"line":124},[64,1022,456],{"class":70},[64,1024,79],{"class":78},[64,1026,1027,1029,1032],{"class":66,"line":135},[64,1028,583],{"class":70},[64,1030,1031],{"class":78}," f.read().upper()    ",[64,1033,1034],{"class":226},"# this becomes the value bound by `as`\n",[64,1036,1037,1039],{"class":66,"line":150},[64,1038,824],{"class":70},[64,1040,79],{"class":78},[64,1042,1043],{"class":66,"line":155},[64,1044,1045],{"class":78},"        f.close()\n",[64,1047,1048],{"class":66,"line":165},[64,1049,121],{"emptyLinePlaceholder":120},[64,1051,1052,1054,1057,1060,1063,1065],{"class":66,"line":171},[64,1053,891],{"class":70},[64,1055,1056],{"class":78}," open_upper(",[64,1058,1059],{"class":85},"\"notes.txt\"",[64,1061,1062],{"class":78},") ",[64,1064,959],{"class":70},[64,1066,1067],{"class":78}," content:\n",[64,1069,1070,1072,1075],{"class":66,"line":180},[64,1071,413],{"class":95},[64,1073,1074],{"class":78},"(content)   ",[64,1076,1077],{"class":226},"# file contents, uppercased\n",[953,1079,1081],{"id":1080},"handling-exceptions-inside-a-generator-based-context-manager","Handling exceptions inside a generator-based context manager",[52,1083,1084],{"language":54},[56,1085,1087],{"className":58,"code":1086,"language":54,"meta":60,"style":60},"from contextlib import contextmanager\n\n@contextmanager\ndef suppress_value_errors():\n    try:\n        yield\n    except ValueError as e:\n        print(f\"Suppressed: {e}\")\n        # NOT re-raising means the exception is swallowed, equivalent to __exit__ returning True\n\nwith suppress_value_errors():\n    int(\"not a number\")\nprint(\"continues normally\")\n",[23,1088,1089,1099,1103,1107,1117,1123,1128,1141,1163,1168,1172,1179,1191],{"__ignoreMap":60},[64,1090,1091,1093,1095,1097],{"class":66,"line":67},[64,1092,761],{"class":70},[64,1094,764],{"class":78},[64,1096,767],{"class":70},[64,1098,770],{"class":78},[64,1100,1101],{"class":66,"line":82},[64,1102,121],{"emptyLinePlaceholder":120},[64,1104,1105],{"class":66,"line":89},[64,1106,786],{"class":74},[64,1108,1109,1111,1114],{"class":66,"line":102},[64,1110,549],{"class":70},[64,1112,1113],{"class":74}," suppress_value_errors",[64,1115,1116],{"class":78},"():\n",[64,1118,1119,1121],{"class":66,"line":117},[64,1120,456],{"class":70},[64,1122,79],{"class":78},[64,1124,1125],{"class":66,"line":124},[64,1126,1127],{"class":70},"        yield\n",[64,1129,1130,1132,1135,1138],{"class":66,"line":135},[64,1131,479],{"class":70},[64,1133,1134],{"class":95}," ValueError",[64,1136,1137],{"class":70}," as",[64,1139,1140],{"class":78}," e:\n",[64,1142,1143,1145,1147,1149,1152,1154,1157,1159,1161],{"class":66,"line":150},[64,1144,847],{"class":95},[64,1146,334],{"class":78},[64,1148,852],{"class":70},[64,1150,1151],{"class":85},"\"Suppressed: ",[64,1153,858],{"class":95},[64,1155,1156],{"class":78},"e",[64,1158,864],{"class":95},[64,1160,855],{"class":85},[64,1162,325],{"class":78},[64,1164,1165],{"class":66,"line":155},[64,1166,1167],{"class":226},"        # NOT re-raising means the exception is swallowed, equivalent to __exit__ returning True\n",[64,1169,1170],{"class":66,"line":165},[64,1171,121],{"emptyLinePlaceholder":120},[64,1173,1174,1176],{"class":66,"line":171},[64,1175,891],{"class":70},[64,1177,1178],{"class":78}," suppress_value_errors():\n",[64,1180,1181,1184,1186,1189],{"class":66,"line":180},[64,1182,1183],{"class":95},"    int",[64,1185,334],{"class":78},[64,1187,1188],{"class":85},"\"not a number\"",[64,1190,325],{"class":78},[64,1192,1193,1195,1197,1200],{"class":66,"line":191},[64,1194,331],{"class":95},[64,1196,334],{"class":78},[64,1198,1199],{"class":85},"\"continues normally\"",[64,1201,325],{"class":78},[19,1203,1204,1205,508,1207,1210,1211,1213,1214,1216,1217,1220,1221,1216,1223,508,1226,1229],{},"If the ",[23,1206,519],{},[23,1208,1209],{},"except"," around ",[23,1212,741],{}," doesn't re-raise, the exception is suppressed — equivalent to a class-based ",[23,1215,945],{}," returning ",[23,1218,1219],{},"True",". Letting the exception propagate (by not catching it, or re-raising) is equivalent to ",[23,1222,945],{},[23,1224,1225],{},"False",[23,1227,1228],{},"None",".",[14,1231,1233,1236],{"id":1232},"contextlibexitstack-managing-a-dynamic-number-of-context-managers",[23,1234,1235],{},"contextlib.ExitStack"," — Managing a Dynamic Number of Context Managers",[52,1238,1239],{"language":54},[56,1240,1242],{"className":58,"code":1241,"language":54,"meta":60,"style":60},"from contextlib import ExitStack\n\npaths = [\"a.txt\", \"b.txt\", \"c.txt\"]\n\nwith ExitStack() as stack:\n    files = [stack.enter_context(open(p, \"w\", encoding=\"utf-8\")) for p in paths]\n    for f in files:\n        f.write(\"data\\n\")\n# ALL files are closed here, in reverse order, even if one of the writes raised partway through\n",[23,1243,1244,1255,1259,1286,1290,1302,1342,1355,1370],{"__ignoreMap":60},[64,1245,1246,1248,1250,1252],{"class":66,"line":67},[64,1247,761],{"class":70},[64,1249,764],{"class":78},[64,1251,767],{"class":70},[64,1253,1254],{"class":78}," ExitStack\n",[64,1256,1257],{"class":66,"line":82},[64,1258,121],{"emptyLinePlaceholder":120},[64,1260,1261,1264,1266,1269,1272,1275,1278,1280,1283],{"class":66,"line":89},[64,1262,1263],{"class":78},"paths ",[64,1265,111],{"class":70},[64,1267,1268],{"class":78}," [",[64,1270,1271],{"class":85},"\"a.txt\"",[64,1273,1274],{"class":78},", ",[64,1276,1277],{"class":85},"\"b.txt\"",[64,1279,1274],{"class":78},[64,1281,1282],{"class":85},"\"c.txt\"",[64,1284,1285],{"class":78},"]\n",[64,1287,1288],{"class":66,"line":102},[64,1289,121],{"emptyLinePlaceholder":120},[64,1291,1292,1294,1297,1299],{"class":66,"line":117},[64,1293,891],{"class":70},[64,1295,1296],{"class":78}," ExitStack() ",[64,1298,959],{"class":70},[64,1300,1301],{"class":78}," stack:\n",[64,1303,1304,1307,1309,1312,1315,1318,1321,1323,1325,1327,1329,1332,1334,1337,1339],{"class":66,"line":124},[64,1305,1306],{"class":78},"    files ",[64,1308,111],{"class":70},[64,1310,1311],{"class":78}," [stack.enter_context(",[64,1313,1314],{"class":95},"open",[64,1316,1317],{"class":78},"(p, ",[64,1319,1320],{"class":85},"\"w\"",[64,1322,1274],{"class":78},[64,1324,1011],{"class":1010},[64,1326,111],{"class":70},[64,1328,1016],{"class":85},[64,1330,1331],{"class":78},")) ",[64,1333,25],{"class":70},[64,1335,1336],{"class":78}," p ",[64,1338,405],{"class":70},[64,1340,1341],{"class":78}," paths]\n",[64,1343,1344,1347,1350,1352],{"class":66,"line":135},[64,1345,1346],{"class":70},"    for",[64,1348,1349],{"class":78}," f ",[64,1351,405],{"class":70},[64,1353,1354],{"class":78}," files:\n",[64,1356,1357,1360,1363,1366,1368],{"class":66,"line":150},[64,1358,1359],{"class":78},"        f.write(",[64,1361,1362],{"class":85},"\"data",[64,1364,1365],{"class":95},"\\n",[64,1367,855],{"class":85},[64,1369,325],{"class":78},[64,1371,1372],{"class":66,"line":155},[64,1373,1374],{"class":226},"# ALL files are closed here, in reverse order, even if one of the writes raised partway through\n",[19,1376,1377,1378,1381,1382,1384,1385,508,1387,1389,1390,1393],{},"Without ",[23,1379,1380],{},"ExitStack",", managing an unknown-at-write-time number of context managers requires either nested nested nested ",[23,1383,891],{}," statements (impossible when the count is dynamic) or manual ",[23,1386,519],{},[23,1388,941],{}," bookkeeping. ",[23,1391,1392],{},"ExitStack.enter_context()"," registers each context manager and guarantees all of them exit cleanly, in reverse order, when the stack itself exits — even if opening the third file fails, the first two are still closed correctly.",[52,1395,1396],{"language":54},[56,1397,1399],{"className":58,"code":1398,"language":54,"meta":60,"style":60},"from contextlib import ExitStack\n\ndef process_all(paths):\n    with ExitStack() as stack:\n        files = []\n        for p in paths:\n            f = stack.enter_context(open(p, encoding=\"utf-8\"))   # each open() registered immediately\n            files.append(f)\n        return [f.read() for f in files]\n",[23,1400,1401,1411,1415,1425,1436,1446,1458,1484,1489],{"__ignoreMap":60},[64,1402,1403,1405,1407,1409],{"class":66,"line":67},[64,1404,761],{"class":70},[64,1406,764],{"class":78},[64,1408,767],{"class":70},[64,1410,1254],{"class":78},[64,1412,1413],{"class":66,"line":82},[64,1414,121],{"emptyLinePlaceholder":120},[64,1416,1417,1419,1422],{"class":66,"line":89},[64,1418,549],{"class":70},[64,1420,1421],{"class":74}," process_all",[64,1423,1424],{"class":78},"(paths):\n",[64,1426,1427,1430,1432,1434],{"class":66,"line":102},[64,1428,1429],{"class":70},"    with",[64,1431,1296],{"class":78},[64,1433,959],{"class":70},[64,1435,1301],{"class":78},[64,1437,1438,1441,1443],{"class":66,"line":117},[64,1439,1440],{"class":78},"        files ",[64,1442,111],{"class":70},[64,1444,1445],{"class":78}," []\n",[64,1447,1448,1451,1453,1455],{"class":66,"line":124},[64,1449,1450],{"class":70},"        for",[64,1452,1336],{"class":78},[64,1454,405],{"class":70},[64,1456,1457],{"class":78}," paths:\n",[64,1459,1460,1463,1465,1468,1470,1472,1474,1476,1478,1481],{"class":66,"line":135},[64,1461,1462],{"class":78},"            f ",[64,1464,111],{"class":70},[64,1466,1467],{"class":78}," stack.enter_context(",[64,1469,1314],{"class":95},[64,1471,1317],{"class":78},[64,1473,1011],{"class":1010},[64,1475,111],{"class":70},[64,1477,1016],{"class":85},[64,1479,1480],{"class":78},"))   ",[64,1482,1483],{"class":226},"# each open() registered immediately\n",[64,1485,1486],{"class":66,"line":150},[64,1487,1488],{"class":78},"            files.append(f)\n",[64,1490,1491,1493,1496,1498,1500,1502],{"class":66,"line":155},[64,1492,138],{"class":70},[64,1494,1495],{"class":78}," [f.read() ",[64,1497,25],{"class":70},[64,1499,1349],{"class":78},[64,1501,405],{"class":70},[64,1503,1504],{"class":78}," files]\n",[14,1506,1508,1511],{"id":1507},"contextlibsuppress-silencing-specific-exceptions-cleanly",[23,1509,1510],{},"contextlib.suppress"," — Silencing Specific Exceptions Cleanly",[52,1513,1514],{"language":54},[56,1515,1517],{"className":58,"code":1516,"language":54,"meta":60,"style":60},"from contextlib import suppress\nimport os\n\n# WRONG-ish (verbose, easy to over-broaden accidentally)\ntry:\n    os.remove(\"maybe_missing.txt\")\nexcept FileNotFoundError:\n    pass\n\n# CLEANER — same exact semantics, more declarative, and impossible to accidentally widen the except clause\nwith suppress(FileNotFoundError):\n    os.remove(\"maybe_missing.txt\")\n",[23,1518,1519,1530,1537,1541,1546,1552,1562,1571,1576,1580,1585,1597],{"__ignoreMap":60},[64,1520,1521,1523,1525,1527],{"class":66,"line":67},[64,1522,761],{"class":70},[64,1524,764],{"class":78},[64,1526,767],{"class":70},[64,1528,1529],{"class":78}," suppress\n",[64,1531,1532,1534],{"class":66,"line":82},[64,1533,767],{"class":70},[64,1535,1536],{"class":78}," os\n",[64,1538,1539],{"class":66,"line":89},[64,1540,121],{"emptyLinePlaceholder":120},[64,1542,1543],{"class":66,"line":102},[64,1544,1545],{"class":226},"# WRONG-ish (verbose, easy to over-broaden accidentally)\n",[64,1547,1548,1550],{"class":66,"line":117},[64,1549,519],{"class":70},[64,1551,79],{"class":78},[64,1553,1554,1557,1560],{"class":66,"line":124},[64,1555,1556],{"class":78},"    os.remove(",[64,1558,1559],{"class":85},"\"maybe_missing.txt\"",[64,1561,325],{"class":78},[64,1563,1564,1566,1569],{"class":66,"line":135},[64,1565,1209],{"class":70},[64,1567,1568],{"class":95}," FileNotFoundError",[64,1570,79],{"class":78},[64,1572,1573],{"class":66,"line":150},[64,1574,1575],{"class":70},"    pass\n",[64,1577,1578],{"class":66,"line":155},[64,1579,121],{"emptyLinePlaceholder":120},[64,1581,1582],{"class":66,"line":165},[64,1583,1584],{"class":226},"# CLEANER — same exact semantics, more declarative, and impossible to accidentally widen the except clause\n",[64,1586,1587,1589,1592,1595],{"class":66,"line":171},[64,1588,891],{"class":70},[64,1590,1591],{"class":78}," suppress(",[64,1593,1594],{"class":95},"FileNotFoundError",[64,1596,900],{"class":78},[64,1598,1599,1601,1603],{"class":66,"line":180},[64,1600,1556],{"class":78},[64,1602,1559],{"class":85},[64,1604,325],{"class":78},[19,1606,1607,1609,1610,508,1612,1615,1616,1620,1621,508,1623,1625],{},[23,1608,1510],{}," is only ever a readability improvement over ",[23,1611,519],{},[23,1613,1614],{},"except: pass"," — it changes nothing about ",[1617,1618,1619],"em",{},"which"," exceptions are caught, but its declarative style makes \"we are deliberately ignoring exactly this error\" visually obvious at a glance, which a ",[23,1622,519],{},[23,1624,1614],{}," block can sometimes obscure if it grows additional lines.",[14,1627,1629,1632],{"id":1628},"itertools-deep-dive-building-blocks-for-iterator-pipelines",[23,1630,1631],{},"itertools"," Deep Dive: Building Blocks for Iterator Pipelines",[19,1634,1635,1636,1638],{},"Chapter 09 introduced ",[23,1637,1631],{},"; here are the pipeline-composition patterns that show its real power.",[52,1640,1641],{"language":54},[56,1642,1644],{"className":58,"code":1643,"language":54,"meta":60,"style":60},"import itertools\n\n# tee — split ONE iterator into N independent ones (each consumer advances independently)\nsource = (x for x in range(5))\na, b = itertools.tee(source, 2)\nprint(list(a))   # [0, 1, 2, 3, 4]\nprint(list(b))     # [0, 1, 2, 3, 4] — b is UNAFFECTED by a being fully consumed first\n\n# chain.from_iterable — flatten one level of nested iterables, lazily\nnested = [[1, 2], [3, 4], [5]]\nprint(list(itertools.chain.from_iterable(nested)))   # [1, 2, 3, 4, 5]\n\n# accumulate — running totals (or running any binary operation)\nprint(list(itertools.accumulate([1, 2, 3, 4])))                  # [1, 3, 6, 10]\nprint(list(itertools.accumulate([1, 2, 3, 4], func=lambda a, b: a * b)))  # [1, 2, 6, 24]\n\n# pairwise (3.10+) — consecutive overlapping pairs\nprint(list(itertools.pairwise([1, 2, 3, 4])))   # [(1, 2), (2, 3), (3, 4)]\n\n# starmap — like map, but unpacks each item as *args\npoints = [(1, 2), (3, 4), (5, 6)]\nprint(list(itertools.starmap(lambda x, y: x + y, points)))   # [3, 7, 11]\n",[23,1645,1646,1653,1657,1662,1689,1704,1718,1732,1736,1741,1775,1789,1793,1798,1829,1874,1878,1883,1914,1918,1923,1960],{"__ignoreMap":60},[64,1647,1648,1650],{"class":66,"line":67},[64,1649,767],{"class":70},[64,1651,1652],{"class":78}," itertools\n",[64,1654,1655],{"class":66,"line":82},[64,1656,121],{"emptyLinePlaceholder":120},[64,1658,1659],{"class":66,"line":89},[64,1660,1661],{"class":226},"# tee — split ONE iterator into N independent ones (each consumer advances independently)\n",[64,1663,1664,1667,1669,1672,1674,1677,1679,1682,1684,1687],{"class":66,"line":102},[64,1665,1666],{"class":78},"source ",[64,1668,111],{"class":70},[64,1670,1671],{"class":78}," (x ",[64,1673,25],{"class":70},[64,1675,1676],{"class":78}," x ",[64,1678,405],{"class":70},[64,1680,1681],{"class":95}," range",[64,1683,334],{"class":78},[64,1685,1686],{"class":95},"5",[64,1688,923],{"class":78},[64,1690,1691,1694,1696,1699,1702],{"class":66,"line":117},[64,1692,1693],{"class":78},"a, b ",[64,1695,111],{"class":70},[64,1697,1698],{"class":78}," itertools.tee(source, ",[64,1700,1701],{"class":95},"2",[64,1703,325],{"class":78},[64,1705,1706,1708,1710,1712,1715],{"class":66,"line":124},[64,1707,331],{"class":95},[64,1709,334],{"class":78},[64,1711,337],{"class":95},[64,1713,1714],{"class":78},"(a))   ",[64,1716,1717],{"class":226},"# [0, 1, 2, 3, 4]\n",[64,1719,1720,1722,1724,1726,1729],{"class":66,"line":135},[64,1721,331],{"class":95},[64,1723,334],{"class":78},[64,1725,337],{"class":95},[64,1727,1728],{"class":78},"(b))     ",[64,1730,1731],{"class":226},"# [0, 1, 2, 3, 4] — b is UNAFFECTED by a being fully consumed first\n",[64,1733,1734],{"class":66,"line":150},[64,1735,121],{"emptyLinePlaceholder":120},[64,1737,1738],{"class":66,"line":155},[64,1739,1740],{"class":226},"# chain.from_iterable — flatten one level of nested iterables, lazily\n",[64,1742,1743,1746,1748,1751,1754,1756,1758,1761,1763,1765,1768,1770,1772],{"class":66,"line":165},[64,1744,1745],{"class":78},"nested ",[64,1747,111],{"class":70},[64,1749,1750],{"class":78}," [[",[64,1752,1753],{"class":95},"1",[64,1755,1274],{"class":78},[64,1757,1701],{"class":95},[64,1759,1760],{"class":78},"], [",[64,1762,322],{"class":95},[64,1764,1274],{"class":78},[64,1766,1767],{"class":95},"4",[64,1769,1760],{"class":78},[64,1771,1686],{"class":95},[64,1773,1774],{"class":78},"]]\n",[64,1776,1777,1779,1781,1783,1786],{"class":66,"line":171},[64,1778,331],{"class":95},[64,1780,334],{"class":78},[64,1782,337],{"class":95},[64,1784,1785],{"class":78},"(itertools.chain.from_iterable(nested)))   ",[64,1787,1788],{"class":226},"# [1, 2, 3, 4, 5]\n",[64,1790,1791],{"class":66,"line":180},[64,1792,121],{"emptyLinePlaceholder":120},[64,1794,1795],{"class":66,"line":191},[64,1796,1797],{"class":226},"# accumulate — running totals (or running any binary operation)\n",[64,1799,1800,1802,1804,1806,1809,1811,1813,1815,1817,1819,1821,1823,1826],{"class":66,"line":204},[64,1801,331],{"class":95},[64,1803,334],{"class":78},[64,1805,337],{"class":95},[64,1807,1808],{"class":78},"(itertools.accumulate([",[64,1810,1753],{"class":95},[64,1812,1274],{"class":78},[64,1814,1701],{"class":95},[64,1816,1274],{"class":78},[64,1818,322],{"class":95},[64,1820,1274],{"class":78},[64,1822,1767],{"class":95},[64,1824,1825],{"class":78},"])))                  ",[64,1827,1828],{"class":226},"# [1, 3, 6, 10]\n",[64,1830,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853,1856,1859,1862,1865,1868,1871],{"class":66,"line":209},[64,1832,331],{"class":95},[64,1834,334],{"class":78},[64,1836,337],{"class":95},[64,1838,1808],{"class":78},[64,1840,1753],{"class":95},[64,1842,1274],{"class":78},[64,1844,1701],{"class":95},[64,1846,1274],{"class":78},[64,1848,322],{"class":95},[64,1850,1274],{"class":78},[64,1852,1767],{"class":95},[64,1854,1855],{"class":78},"], ",[64,1857,1858],{"class":1010},"func",[64,1860,1861],{"class":70},"=lambda",[64,1863,1864],{"class":78}," a, b: a ",[64,1866,1867],{"class":70},"*",[64,1869,1870],{"class":78}," b)))  ",[64,1872,1873],{"class":226},"# [1, 2, 6, 24]\n",[64,1875,1876],{"class":66,"line":218},[64,1877,121],{"emptyLinePlaceholder":120},[64,1879,1880],{"class":66,"line":230},[64,1881,1882],{"class":226},"# pairwise (3.10+) — consecutive overlapping pairs\n",[64,1884,1885,1887,1889,1891,1894,1896,1898,1900,1902,1904,1906,1908,1911],{"class":66,"line":235},[64,1886,331],{"class":95},[64,1888,334],{"class":78},[64,1890,337],{"class":95},[64,1892,1893],{"class":78},"(itertools.pairwise([",[64,1895,1753],{"class":95},[64,1897,1274],{"class":78},[64,1899,1701],{"class":95},[64,1901,1274],{"class":78},[64,1903,322],{"class":95},[64,1905,1274],{"class":78},[64,1907,1767],{"class":95},[64,1909,1910],{"class":78},"])))   ",[64,1912,1913],{"class":226},"# [(1, 2), (2, 3), (3, 4)]\n",[64,1915,1916],{"class":66,"line":245},[64,1917,121],{"emptyLinePlaceholder":120},[64,1919,1920],{"class":66,"line":263},[64,1921,1922],{"class":226},"# starmap — like map, but unpacks each item as *args\n",[64,1924,1925,1928,1930,1933,1935,1937,1939,1942,1944,1946,1948,1950,1952,1954,1957],{"class":66,"line":272},[64,1926,1927],{"class":78},"points ",[64,1929,111],{"class":70},[64,1931,1932],{"class":78}," [(",[64,1934,1753],{"class":95},[64,1936,1274],{"class":78},[64,1938,1701],{"class":95},[64,1940,1941],{"class":78},"), (",[64,1943,322],{"class":95},[64,1945,1274],{"class":78},[64,1947,1767],{"class":95},[64,1949,1941],{"class":78},[64,1951,1686],{"class":95},[64,1953,1274],{"class":78},[64,1955,1956],{"class":95},"6",[64,1958,1959],{"class":78},")]\n",[64,1961,1962,1964,1966,1968,1971,1974,1977,1980,1983],{"class":66,"line":285},[64,1963,331],{"class":95},[64,1965,334],{"class":78},[64,1967,337],{"class":95},[64,1969,1970],{"class":78},"(itertools.starmap(",[64,1972,1973],{"class":70},"lambda",[64,1975,1976],{"class":78}," x, y: x ",[64,1978,1979],{"class":70},"+",[64,1981,1982],{"class":78}," y, points)))   ",[64,1984,1985],{"class":226},"# [3, 7, 11]\n",[953,1987,1989,1990,1993],{"id":1988},"the-tee-gotcha-dont-use-the-original-iterator-after-teeing","The ",[23,1991,1992],{},"tee"," gotcha: don't use the original iterator after teeing",[52,1995,1996],{"language":54},[56,1997,1999],{"className":58,"code":1998,"language":54,"meta":60,"style":60},"source = iter([1, 2, 3])\na, b = itertools.tee(source, 2)\nnext(source)      # DON'T do this — advances the shared underlying iterator behind tee's back\nprint(list(a))      # [2, 3] — missing 1! tee's internal buffering is now inconsistent\n",[23,2000,2001,2025,2037,2047],{"__ignoreMap":60},[64,2002,2003,2005,2007,2009,2012,2014,2016,2018,2020,2022],{"class":66,"line":67},[64,2004,1666],{"class":78},[64,2006,111],{"class":70},[64,2008,435],{"class":95},[64,2010,2011],{"class":78},"([",[64,2013,1753],{"class":95},[64,2015,1274],{"class":78},[64,2017,1701],{"class":95},[64,2019,1274],{"class":78},[64,2021,322],{"class":95},[64,2023,2024],{"class":78},"])\n",[64,2026,2027,2029,2031,2033,2035],{"class":66,"line":82},[64,2028,1693],{"class":78},[64,2030,111],{"class":70},[64,2032,1698],{"class":78},[64,2034,1701],{"class":95},[64,2036,325],{"class":78},[64,2038,2039,2041,2044],{"class":66,"line":89},[64,2040,686],{"class":95},[64,2042,2043],{"class":78},"(source)      ",[64,2045,2046],{"class":226},"# DON'T do this — advances the shared underlying iterator behind tee's back\n",[64,2048,2049,2051,2053,2055,2058],{"class":66,"line":102},[64,2050,331],{"class":95},[64,2052,334],{"class":78},[64,2054,337],{"class":95},[64,2056,2057],{"class":78},"(a))      ",[64,2059,2060],{"class":226},"# [2, 3] — missing 1! tee's internal buffering is now inconsistent\n",[19,2062,2063,2064,2066,2067,2069,2070,2073],{},"Once an iterator is passed to ",[23,2065,1992],{},", treat the original as consumed\u002Foff-limits — ",[23,2068,1992],{}," internally buffers items so each derived iterator can proceed independently, but that buffering assumes it's the ",[1617,2071,2072],{},"only"," thing pulling from the source.",[14,2075,2077,2078,508,2080],{"id":2076},"building-a-reusable-class-based-context-manager-with-__enter____exit__","Building a Reusable, Class-Based Context Manager With ",[23,2079,937],{},[23,2081,945],{},[19,2083,2084,2085,2087,2088,2091],{},"For cases needing more state or reuse across many ",[23,2086,891],{}," blocks with configuration, a class is still the better tool than ",[23,2089,2090],{},"@contextmanager",":",[52,2093,2094],{"language":54},[56,2095,2097],{"className":58,"code":2096,"language":54,"meta":60,"style":60},"class Transaction:\n    def __init__(self, connection):\n        self.connection = connection\n\n    def __enter__(self):\n        self.connection.begin()\n        return self.connection\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        if exc_type is None:\n            self.connection.commit()\n        else:\n            self.connection.rollback()\n        return False   # never suppress — let the caller see what went wrong\n\n# with Transaction(db_connection) as conn:\n#     conn.execute(\"UPDATE accounts SET balance = balance - 100 WHERE id = 1\")\n#     conn.execute(\"UPDATE accounts SET balance = balance + 100 WHERE id = 2\")\n# commits if both succeed, rolls back automatically if either raises\n",[23,2098,2099,2108,2117,2129,2133,2142,2149,2158,2162,2172,2186,2194,2201,2208,2218,2222,2227,2232,2237],{"__ignoreMap":60},[64,2100,2101,2103,2106],{"class":66,"line":67},[64,2102,71],{"class":70},[64,2104,2105],{"class":74}," Transaction",[64,2107,79],{"class":78},[64,2109,2110,2112,2114],{"class":66,"line":82},[64,2111,92],{"class":70},[64,2113,96],{"class":95},[64,2115,2116],{"class":78},"(self, connection):\n",[64,2118,2119,2121,2124,2126],{"class":66,"line":89},[64,2120,105],{"class":95},[64,2122,2123],{"class":78},".connection ",[64,2125,111],{"class":70},[64,2127,2128],{"class":78}," connection\n",[64,2130,2131],{"class":66,"line":102},[64,2132,121],{"emptyLinePlaceholder":120},[64,2134,2135,2137,2140],{"class":66,"line":117},[64,2136,92],{"class":70},[64,2138,2139],{"class":95}," __enter__",[64,2141,132],{"class":78},[64,2143,2144,2146],{"class":66,"line":124},[64,2145,105],{"class":95},[64,2147,2148],{"class":78},".connection.begin()\n",[64,2150,2151,2153,2155],{"class":66,"line":135},[64,2152,138],{"class":70},[64,2154,223],{"class":95},[64,2156,2157],{"class":78},".connection\n",[64,2159,2160],{"class":66,"line":150},[64,2161,121],{"emptyLinePlaceholder":120},[64,2163,2164,2166,2169],{"class":66,"line":155},[64,2165,92],{"class":70},[64,2167,2168],{"class":95}," __exit__",[64,2170,2171],{"class":78},"(self, exc_type, exc_value, traceback):\n",[64,2173,2174,2176,2179,2181,2184],{"class":66,"line":165},[64,2175,248],{"class":70},[64,2177,2178],{"class":78}," exc_type ",[64,2180,667],{"class":70},[64,2182,2183],{"class":95}," None",[64,2185,79],{"class":78},[64,2187,2188,2191],{"class":66,"line":171},[64,2189,2190],{"class":95},"            self",[64,2192,2193],{"class":78},".connection.commit()\n",[64,2195,2196,2199],{"class":66,"line":180},[64,2197,2198],{"class":70},"        else",[64,2200,79],{"class":78},[64,2202,2203,2205],{"class":66,"line":191},[64,2204,2190],{"class":95},[64,2206,2207],{"class":78},".connection.rollback()\n",[64,2209,2210,2212,2215],{"class":66,"line":204},[64,2211,138],{"class":70},[64,2213,2214],{"class":95}," False",[64,2216,2217],{"class":226},"   # never suppress — let the caller see what went wrong\n",[64,2219,2220],{"class":66,"line":209},[64,2221,121],{"emptyLinePlaceholder":120},[64,2223,2224],{"class":66,"line":218},[64,2225,2226],{"class":226},"# with Transaction(db_connection) as conn:\n",[64,2228,2229],{"class":66,"line":230},[64,2230,2231],{"class":226},"#     conn.execute(\"UPDATE accounts SET balance = balance - 100 WHERE id = 1\")\n",[64,2233,2234],{"class":66,"line":235},[64,2235,2236],{"class":226},"#     conn.execute(\"UPDATE accounts SET balance = balance + 100 WHERE id = 2\")\n",[64,2238,2239],{"class":66,"line":245},[64,2240,2241],{"class":226},"# commits if both succeed, rolls back automatically if either raises\n",[19,2243,2244,2245,2247,2248,2251,2252,2254],{},"This is the canonical database-transaction pattern: ",[23,2246,945],{}," inspects ",[23,2249,2250],{},"exc_type"," to decide between commit and rollback, and returns ",[23,2253,1225],{}," so any real error still propagates to the caller instead of being silently absorbed.",[14,2256,2258],{"id":2257},"tips-tricks","💡 Tips & Tricks",[2260,2261,2262,2280,2303,2322,2337],"ul",{},[2263,2264,2265,2268,2269,2271,2272,508,2274,2276,2277,2279],"li",{},[28,2266,2267],{},"Idiom",": reach for ",[23,2270,2090],{}," first for any new context manager — only drop to a full ",[23,2273,937],{},[23,2275,945],{}," class when you need to reuse the same instance across multiple ",[23,2278,891],{}," blocks or need extra methods\u002Fstate beyond simple setup\u002Fteardown.",[2263,2281,2282,867,2285,2288,2289,2291,2292,2295,2296,2298,2299,508,2301,1229],{},[28,2283,2284],{},"Debug",[23,2286,2287],{},"next(iterator, default)"," — the two-argument form of ",[23,2290,504],{}," — returns ",[23,2293,2294],{},"default"," instead of raising ",[23,2297,49],{}," when exhausted, useful for \"peek and maybe stop\" logic without a ",[23,2300,519],{},[23,2302,1209],{},[2263,2304,2305,867,2308,2310,2311,2314,2315,2314,2318,2321],{},[28,2306,2307],{},"Performance",[23,2309,1631],{}," functions are implemented in C and are lazy — chaining several of them (",[23,2312,2313],{},"filter"," → ",[23,2316,2317],{},"map",[23,2319,2320],{},"itertools.islice",") processes one item at a time through the whole pipeline, using far less memory than building intermediate lists at each stage.",[2263,2323,2324,867,2326,2329,2330,2332,2333,2336],{},[28,2325,2267],{},[23,2327,2328],{},"contextlib.suppress(*exceptions)"," accepts multiple exception types just like a tuple in ",[23,2331,1209],{}," — ",[23,2334,2335],{},"suppress(FileNotFoundError, PermissionError)"," — for concisely ignoring more than one specific, expected failure.",[2263,2338,2339,2341,2342,2344,2345,2348,2349,2351,2352,2354,2355,2357],{},[28,2340,2284],{},": if a ",[23,2343,25],{}," loop over a custom object raises ",[23,2346,2347],{},"TypeError: object is not iterable",", check for a missing ",[23,2350,34],{}," (and, if hand-rolling an iterator instead of a generator, a missing ",[23,2353,45],{}," that raises ",[23,2356,49],{},") — both protocol methods are required and Python doesn't infer one from the other.",[14,2359,2361],{"id":2360},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2260,2363,2364,2384,2400,2429,2450],{},[2263,2365,2366,2373,2374,2376,2377,2380,2381,2383],{},[28,2367,2368,2369,2372],{},"A generator IS its own iterator (",[23,2370,2371],{},"iter(gen) is gen","), which is exactly why it's single-use"," — a plain iterable class with ",[23,2375,34],{}," returning a ",[1617,2378,2379],{},"new"," iterator instance each call is reusable across multiple ",[23,2382,25],{}," loops; conflating the two is a common source of the \"why did my second loop get nothing\" bug from chapter 09.",[2263,2385,2386,2393,2394,2396,2397,2399],{},[28,2387,2388,2389,2392],{},"Using the original iterator after passing it to ",[23,2390,2391],{},"itertools.tee"," silently corrupts the derived iterators' output",", since ",[23,2395,1992],{},"'s internal buffering assumes exclusive access to the source from that point forward — always stop using the original reference once ",[23,2398,1992],{}," has been called on it.",[2263,2401,2402,2420,2421,2423,2424,2426,2427,1229],{},[28,2403,2404,2405,2408,2409,2411,2412,508,2414,2416,2417,2419],{},"A ",[23,2406,2407],{},"contextmanager","-decorated generator function that doesn't wrap ",[23,2410,741],{}," in ",[23,2413,519],{},[23,2415,941],{}," will skip its cleanup code entirely if the ",[23,2418,891],{}," block raises"," — unlike a plain function, an unhandled exception propagating through the ",[23,2422,741],{}," point means the code after ",[23,2425,741],{}," never executes unless it's specifically inside a ",[23,2428,941],{},[2263,2430,2431,2440,2441,2443,2444,2446,2447,2449],{},[28,2432,2433,2434,2436,2437,2439],{},"Calling ",[23,2435,504],{}," on an exhausted iterator raises ",[23,2438,49],{}," every time, not just once"," — code that calls ",[23,2442,504],{}," directly (rather than iterating via ",[23,2445,25],{},", which catches ",[23,2448,49],{}," automatically) must handle this explicitly, or the exception will propagate as an apparent bug.",[2263,2451,2452,2467],{},[28,2453,2404,2454,2456,2457,2459,2460,2463,2464,2466],{},[23,2455,2407],{},"-decorated generator must ",[23,2458,741],{}," exactly once — yielding zero times or more than once raises ",[23,2461,2462],{},"RuntimeError"," at ",[23,2465,891],{},"-statement time",", a mistake that's easy to introduce by accident inside a loop or conditional inside the generator body.",[14,2469,2471],{"id":2470},"spot-the-bug","🧠 Spot the Bug",[19,2473,2474],{},"A file-processing utility wraps cleanup logic in a custom context manager, but resources leak under certain failure conditions. Find the bug.",[52,2476,2477],{"language":54},[56,2478,2480],{"className":58,"code":2479,"language":54,"meta":60,"style":60},"from contextlib import contextmanager\n\n@contextmanager\ndef open_many(paths):\n    files = [open(p, encoding=\"utf-8\") for p in paths]\n    yield files\n    for f in files:\n        f.close()\n\nwith open_many([\"a.txt\", \"b.txt\", \"c.txt\"]) as files:\n    for f in files:\n        process(f)   # suppose process() raises on the second file\n",[23,2481,2482,2492,2496,2500,2509,2537,2545,2555,2559,2563,2587,2597],{"__ignoreMap":60},[64,2483,2484,2486,2488,2490],{"class":66,"line":67},[64,2485,761],{"class":70},[64,2487,764],{"class":78},[64,2489,767],{"class":70},[64,2491,770],{"class":78},[64,2493,2494],{"class":66,"line":82},[64,2495,121],{"emptyLinePlaceholder":120},[64,2497,2498],{"class":66,"line":89},[64,2499,786],{"class":74},[64,2501,2502,2504,2507],{"class":66,"line":102},[64,2503,549],{"class":70},[64,2505,2506],{"class":74}," open_many",[64,2508,1424],{"class":78},[64,2510,2511,2513,2515,2517,2519,2521,2523,2525,2527,2529,2531,2533,2535],{"class":66,"line":117},[64,2512,1306],{"class":78},[64,2514,111],{"class":70},[64,2516,1268],{"class":78},[64,2518,1314],{"class":95},[64,2520,1317],{"class":78},[64,2522,1011],{"class":1010},[64,2524,111],{"class":70},[64,2526,1016],{"class":85},[64,2528,1062],{"class":78},[64,2530,25],{"class":70},[64,2532,1336],{"class":78},[64,2534,405],{"class":70},[64,2536,1341],{"class":78},[64,2538,2539,2542],{"class":66,"line":124},[64,2540,2541],{"class":70},"    yield",[64,2543,2544],{"class":78}," files\n",[64,2546,2547,2549,2551,2553],{"class":66,"line":135},[64,2548,1346],{"class":70},[64,2550,1349],{"class":78},[64,2552,405],{"class":70},[64,2554,1354],{"class":78},[64,2556,2557],{"class":66,"line":150},[64,2558,1045],{"class":78},[64,2560,2561],{"class":66,"line":155},[64,2562,121],{"emptyLinePlaceholder":120},[64,2564,2565,2567,2570,2572,2574,2576,2578,2580,2583,2585],{"class":66,"line":165},[64,2566,891],{"class":70},[64,2568,2569],{"class":78}," open_many([",[64,2571,1271],{"class":85},[64,2573,1274],{"class":78},[64,2575,1277],{"class":85},[64,2577,1274],{"class":78},[64,2579,1282],{"class":85},[64,2581,2582],{"class":78},"]) ",[64,2584,959],{"class":70},[64,2586,1354],{"class":78},[64,2588,2589,2591,2593,2595],{"class":66,"line":171},[64,2590,1346],{"class":70},[64,2592,1349],{"class":78},[64,2594,405],{"class":70},[64,2596,1354],{"class":78},[64,2598,2599,2602],{"class":66,"line":180},[64,2600,2601],{"class":78},"        process(f)   ",[64,2603,2604],{"class":226},"# suppose process() raises on the second file\n",[2606,2607,2608,2612,2653,2666,2746,2756,2834],"details",{},[2609,2610,2611],"summary",{},"Answer",[19,2613,2614,2615,2618,2619,2622,2623,2625,2626,2629,2630,2633,2634,2637,2638,2641,2642,2644,2645,508,2647,2649,2650,2652],{},"If ",[23,2616,2617],{},"process(f)"," raises while iterating ",[23,2620,2621],{},"files"," inside the ",[23,2624,891],{}," block, the exception propagates up through the ",[23,2627,2628],{},"yield files"," line inside ",[23,2631,2632],{},"open_many"," — but the cleanup loop (",[23,2635,2636],{},"for f in files: f.close()",") is written ",[1617,2639,2640],{},"after"," ",[23,2643,741],{},", with no ",[23,2646,519],{},[23,2648,941],{}," around it. Since the exception isn't caught, execution never reaches the ",[23,2651,2636],{}," line at all — every opened file handle leaks (stays open) for the duration of the process, only eventually cleaned up (if ever) by garbage collection or process exit, not deterministically.",[19,2654,2655,2656,2658,2659,2411,2661,508,2663,2665],{},"The fix is the same rule as any ",[23,2657,2090],{},": wrap ",[23,2660,741],{},[23,2662,519],{},[23,2664,941],{}," so cleanup always runs, exception or not:",[52,2667,2668],{"language":54},[56,2669,2671],{"className":58,"code":2670,"language":54,"meta":60,"style":60},"@contextmanager\ndef open_many(paths):\n    files = [open(p, encoding=\"utf-8\") for p in paths]\n    try:\n        yield files\n    finally:\n        for f in files:\n            f.close()\n",[23,2672,2673,2677,2685,2713,2719,2725,2731,2741],{"__ignoreMap":60},[64,2674,2675],{"class":66,"line":67},[64,2676,786],{"class":74},[64,2678,2679,2681,2683],{"class":66,"line":82},[64,2680,549],{"class":70},[64,2682,2506],{"class":74},[64,2684,1424],{"class":78},[64,2686,2687,2689,2691,2693,2695,2697,2699,2701,2703,2705,2707,2709,2711],{"class":66,"line":89},[64,2688,1306],{"class":78},[64,2690,111],{"class":70},[64,2692,1268],{"class":78},[64,2694,1314],{"class":95},[64,2696,1317],{"class":78},[64,2698,1011],{"class":1010},[64,2700,111],{"class":70},[64,2702,1016],{"class":85},[64,2704,1062],{"class":78},[64,2706,25],{"class":70},[64,2708,1336],{"class":78},[64,2710,405],{"class":70},[64,2712,1341],{"class":78},[64,2714,2715,2717],{"class":66,"line":102},[64,2716,456],{"class":70},[64,2718,79],{"class":78},[64,2720,2721,2723],{"class":66,"line":117},[64,2722,583],{"class":70},[64,2724,2544],{"class":78},[64,2726,2727,2729],{"class":66,"line":124},[64,2728,824],{"class":70},[64,2730,79],{"class":78},[64,2732,2733,2735,2737,2739],{"class":66,"line":135},[64,2734,1450],{"class":70},[64,2736,1349],{"class":78},[64,2738,405],{"class":70},[64,2740,1354],{"class":78},[64,2742,2743],{"class":66,"line":150},[64,2744,2745],{"class":78},"            f.close()\n",[19,2747,2748,2749,2751,2752,2755],{},"An even more robust version uses ",[23,2750,1380],{}," so that a failure partway through ",[1617,2753,2754],{},"opening"," the files (not just processing them) still closes whichever ones did open successfully:",[52,2757,2758],{"language":54},[56,2759,2761],{"className":58,"code":2760,"language":54,"meta":60,"style":60},"from contextlib import ExitStack, contextmanager\n\n@contextmanager\ndef open_many(paths):\n    with ExitStack() as stack:\n        files = [stack.enter_context(open(p, encoding=\"utf-8\")) for p in paths]\n        yield files\n",[23,2762,2763,2774,2778,2782,2790,2800,2828],{"__ignoreMap":60},[64,2764,2765,2767,2769,2771],{"class":66,"line":67},[64,2766,761],{"class":70},[64,2768,764],{"class":78},[64,2770,767],{"class":70},[64,2772,2773],{"class":78}," ExitStack, contextmanager\n",[64,2775,2776],{"class":66,"line":82},[64,2777,121],{"emptyLinePlaceholder":120},[64,2779,2780],{"class":66,"line":89},[64,2781,786],{"class":74},[64,2783,2784,2786,2788],{"class":66,"line":102},[64,2785,549],{"class":70},[64,2787,2506],{"class":74},[64,2789,1424],{"class":78},[64,2791,2792,2794,2796,2798],{"class":66,"line":117},[64,2793,1429],{"class":70},[64,2795,1296],{"class":78},[64,2797,959],{"class":70},[64,2799,1301],{"class":78},[64,2801,2802,2804,2806,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826],{"class":66,"line":124},[64,2803,1440],{"class":78},[64,2805,111],{"class":70},[64,2807,1311],{"class":78},[64,2809,1314],{"class":95},[64,2811,1317],{"class":78},[64,2813,1011],{"class":1010},[64,2815,111],{"class":70},[64,2817,1016],{"class":85},[64,2819,1331],{"class":78},[64,2821,25],{"class":70},[64,2823,1336],{"class":78},[64,2825,405],{"class":70},[64,2827,1341],{"class":78},[64,2829,2830,2832],{"class":66,"line":135},[64,2831,583],{"class":70},[64,2833,2544],{"class":78},[19,2835,2836,2839,2840,2842,2843,2845,2846,508,2848,2850,2851,2853],{},[28,2837,2838],{},"The lesson",": any code after a ",[23,2841,2407],{}," generator's ",[23,2844,741],{}," is only cleanup logic if it's guaranteed to run — and the only way to guarantee that in the presence of exceptions is ",[23,2847,519],{},[23,2849,941],{}," (or delegating to ",[23,2852,1380],{},"), exactly as with a plain function's cleanup code.",[14,2855,2857],{"id":2856},"key-takeaways","Key Takeaways",[2260,2859,2860,2885,2894,2917,2922,2933],{},[2263,2861,2862,2863,2865,2866,2868,2869,2871,2872,2874,2875,2877,2878,2880,2881,508,2883,1229],{},"The iterator protocol is ",[23,2864,34],{}," (returns an iterator) plus ",[23,2867,45],{}," (returns the next value or raises ",[23,2870,49],{},") — a ",[23,2873,25],{}," loop is sugar for calling ",[23,2876,500],{}," once and ",[23,2879,504],{}," repeatedly inside a ",[23,2882,519],{},[23,2884,522],{},[2263,2886,2887,2888,2890,2891,2893],{},"A generator is its own iterator, which is exactly why it's single-use; an iterable class whose ",[23,2889,34],{}," returns a fresh iterator object each call is reusable across multiple ",[23,2892,25],{}," loops.",[2263,2895,2896,2898,2899,2901,2902,2904,2905,942,2907,2909,2910,508,2912,1210,2914,2916],{},[23,2897,748],{}," turns a single generator function into a context manager — code before ",[23,2900,741],{}," is ",[23,2903,937],{},", code after (inside ",[23,2906,941],{},[23,2908,945],{},"; omitting the ",[23,2911,519],{},[23,2913,941],{},[23,2915,741],{}," means cleanup silently doesn't run on exceptions.",[2263,2918,2919,2921],{},[23,2920,1235],{}," manages a dynamic, runtime-determined number of context managers, guaranteeing all of them clean up in reverse order even if one fails partway through setup.",[2263,2923,2924,2927,2928,508,2930,2932],{},[23,2925,2926],{},"contextlib.suppress(...)"," is a declarative, harder-to-accidentally-broaden alternative to ",[23,2929,519],{},[23,2931,1614],{}," for deliberately ignoring specific, expected exceptions.",[2263,2934,2935,2937],{},[23,2936,2391],{}," splits one iterator into independent ones, but only if you stop using the original iterator afterward — pulling from it directly corrupts the derived iterators' buffering.",[2939,2940,2941],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}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 pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .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);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":60,"searchDepth":82,"depth":82,"links":2943},[2944,2945,2947,2948,2954,2956,2958,2963,2965,2966,2967,2968],{"id":16,"depth":82,"text":17},{"id":379,"depth":82,"text":2946},"What for Actually Does",{"id":536,"depth":82,"text":537},{"id":745,"depth":82,"text":2949,"children":2950},"contextlib.contextmanager — Context Managers Without a Class",[2951,2953],{"id":955,"depth":89,"text":2952},"Yielding a value for as",{"id":1080,"depth":89,"text":1081},{"id":1232,"depth":82,"text":2955},"contextlib.ExitStack — Managing a Dynamic Number of Context Managers",{"id":1507,"depth":82,"text":2957},"contextlib.suppress — Silencing Specific Exceptions Cleanly",{"id":1628,"depth":82,"text":2959,"children":2960},"itertools Deep Dive: Building Blocks for Iterator Pipelines",[2961],{"id":1988,"depth":89,"text":2962},"The tee gotcha: don't use the original iterator after teeing",{"id":2076,"depth":82,"text":2964},"Building a Reusable, Class-Based Context Manager With __enter__\u002F__exit__",{"id":2257,"depth":82,"text":2258},{"id":2360,"depth":82,"text":2361},{"id":2470,"depth":82,"text":2471},{"id":2856,"depth":82,"text":2857},"md",{},"\u002Fpython\u002F19-iterators-and-context-managers",{"title":5,"description":60},"python\u002F19-iterators-and-context-managers","e6zapCkDv_PObh4ScC9GHQByEwaZEszKFbBF53Ktlt8",1789924651661]