[{"data":1,"prerenderedAt":3450},["ShallowReactive",2],{"page-\u002Fpython\u002F05-functions":3},{"id":4,"title":5,"body":6,"description":27,"extension":3444,"meta":3445,"navigation":51,"path":3446,"seo":3447,"stem":3448,"__hash__":3449},"content\u002Fpython\u002F05-functions.md","05 — Functions",{"type":7,"value":8,"toc":3422},"minimark",[9,13,18,878,882,1034,1039,1159,1166,1170,1177,1291,1324,1444,1472,1482,1492,1595,1599,1814,1823,2047,2051,2060,2150,2154,2252,2265,2347,2351,2481,2496,2500,2511,2584,2614,2698,2775,2778,2782,2854,2865,2986,2996,3000,3073,3077,3182,3186,3189,3312,3356,3360,3418],[10,11,5],"h1",{"id":12},"_05-functions",[14,15,17],"h2",{"id":16},"production-grade-signature-forwarding-and-decorator-construction","Production-Grade Signature Forwarding and Decorator Construction",[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 retry decorator with exponential backoff, telemetry, and\n#    full signature preservation — the kind you'd actually ship in a service ──\n\nimport time, logging, functools\nfrom typing import Callable, TypeVar, ParamSpec\n\nP = ParamSpec(\"P\")      # 3.10+ — captures the full parameter signature\nR = TypeVar(\"R\")\n\nlog = logging.getLogger(__name__)\n\ndef retry(\n    max_attempts: int = 3,\n    base_delay: float = 0.1,\n    max_delay: float = 10.0,\n    exceptions: tuple[type[Exception], ...] = (Exception,),\n    on_retry: Callable[[Exception, int], None] | None = None,\n) -> Callable[[Callable[P, R]], Callable[P, R]]:\n    \"\"\"\n    Retry with exponential backoff + jitter.\n\n    Args:\n        max_attempts: Total call attempts (including the first).\n        base_delay: Initial delay in seconds; doubles each retry.\n        max_delay: Cap on backoff delay.\n        exceptions: Only these exception types trigger a retry.\n        on_retry: Optional callback(exc, attempt) for metrics\u002Flogging.\n    \"\"\"\n    def decorator(func: Callable[P, R]) -> Callable[P, R]:\n        @functools.wraps(func)          # preserves __name__, __doc__, __wrapped__, __annotations__\n        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n            last_exc: Exception | None = None\n            for attempt in range(1, max_attempts + 1):\n                try:\n                    return func(*args, **kwargs)\n                except exceptions as exc:\n                    last_exc = exc\n                    if attempt >= max_attempts:\n                        log.error(\"%s failed after %d attempts: %s\", func.__name__, attempt, exc)\n                        raise          # re-raise the last exception — caller sees the real failure\n                    delay = min(base_delay * (2 ** (attempt - 1)), max_delay)\n                    if on_retry:\n                        on_retry(exc, attempt)\n                    log.warning(\"%s attempt %d failed, retrying in %.2fs: %s\", func.__name__, attempt, delay, exc)\n                    time.sleep(delay)\n            raise last_exc  # type: ignore[misc] — unreachable, but satisfies type checker\n        return wrapper\n    return decorator\n\n# Usage — full type safety preserved through the decorator\n@retry(max_attempts=3, exceptions=(ConnectionError, TimeoutError), on_retry=lambda e, a: print(f\"retry {a}: {e}\"))\ndef fetch_user(user_id: int) -> dict:\n    \"\"\"Fetch a user from the API — retries on network errors.\"\"\"\n    import random\n    if random.random() \u003C 0.5:\n        raise ConnectionError(\"network timeout\")\n    return {\"id\": user_id, \"name\": \"Ada\"}\n\n# The wrapped function retains the original signature — inspect sees fetch_user, not `wrapper`\nimport inspect\nprint(inspect.signature(fetch_user))   # (user_id: int) -> dict — preserved by @functools.wraps\n","",[29,30,31,40,46,53,64,78,83,105,122,127,144,149,162,180,196,211,239,271,277,283,289,294,300,306,312,318,324,330,335,347,359,383,401,433,442,461,476,487,501,534,543,578,586,592,626,632,644,653,662,667,673,754,775,781,790,807,823,848,853,859,867],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── A production retry decorator with exponential backoff, telemetry, and\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"#    full signature preservation — the kind you'd actually ship in a service ──\n",[32,47,49],{"class":34,"line":48},3,[32,50,52],{"emptyLinePlaceholder":51},true,"\n",[32,54,56,60],{"class":34,"line":55},4,[32,57,59],{"class":58},"svdQ7","import",[32,61,63],{"class":62},"ssxIu"," time, logging, functools\n",[32,65,67,70,73,75],{"class":34,"line":66},5,[32,68,69],{"class":58},"from",[32,71,72],{"class":62}," typing ",[32,74,59],{"class":58},[32,76,77],{"class":62}," Callable, TypeVar, ParamSpec\n",[32,79,81],{"class":34,"line":80},6,[32,82,52],{"emptyLinePlaceholder":51},[32,84,86,89,92,95,99,102],{"class":34,"line":85},7,[32,87,88],{"class":62},"P ",[32,90,91],{"class":58},"=",[32,93,94],{"class":62}," ParamSpec(",[32,96,98],{"class":97},"sJ6F3","\"P\"",[32,100,101],{"class":62},")      ",[32,103,104],{"class":38},"# 3.10+ — captures the full parameter signature\n",[32,106,108,111,113,116,119],{"class":34,"line":107},8,[32,109,110],{"class":62},"R ",[32,112,91],{"class":58},[32,114,115],{"class":62}," TypeVar(",[32,117,118],{"class":97},"\"R\"",[32,120,121],{"class":62},")\n",[32,123,125],{"class":34,"line":124},9,[32,126,52],{"emptyLinePlaceholder":51},[32,128,130,133,135,138,142],{"class":34,"line":129},10,[32,131,132],{"class":62},"log ",[32,134,91],{"class":58},[32,136,137],{"class":62}," logging.getLogger(",[32,139,141],{"class":140},"snvgF","__name__",[32,143,121],{"class":62},[32,145,147],{"class":34,"line":146},11,[32,148,52],{"emptyLinePlaceholder":51},[32,150,152,155,159],{"class":34,"line":151},12,[32,153,154],{"class":58},"def",[32,156,158],{"class":157},"sIsaT"," retry",[32,160,161],{"class":62},"(\n",[32,163,165,168,171,174,177],{"class":34,"line":164},13,[32,166,167],{"class":62},"    max_attempts: ",[32,169,170],{"class":140},"int",[32,172,173],{"class":58}," =",[32,175,176],{"class":140}," 3",[32,178,179],{"class":62},",\n",[32,181,183,186,189,191,194],{"class":34,"line":182},14,[32,184,185],{"class":62},"    base_delay: ",[32,187,188],{"class":140},"float",[32,190,173],{"class":58},[32,192,193],{"class":140}," 0.1",[32,195,179],{"class":62},[32,197,199,202,204,206,209],{"class":34,"line":198},15,[32,200,201],{"class":62},"    max_delay: ",[32,203,188],{"class":140},[32,205,173],{"class":58},[32,207,208],{"class":140}," 10.0",[32,210,179],{"class":62},[32,212,214,217,220,223,226,229,231,234,236],{"class":34,"line":213},16,[32,215,216],{"class":62},"    exceptions: tuple[type[",[32,218,219],{"class":140},"Exception",[32,221,222],{"class":62},"], ",[32,224,225],{"class":140},"...",[32,227,228],{"class":62},"] ",[32,230,91],{"class":58},[32,232,233],{"class":62}," (",[32,235,219],{"class":140},[32,237,238],{"class":62},",),\n",[32,240,242,245,247,250,252,254,257,259,262,265,267,269],{"class":34,"line":241},17,[32,243,244],{"class":62},"    on_retry: Callable[[",[32,246,219],{"class":140},[32,248,249],{"class":62},", ",[32,251,170],{"class":140},[32,253,222],{"class":62},[32,255,256],{"class":140},"None",[32,258,228],{"class":62},[32,260,261],{"class":58},"|",[32,263,264],{"class":140}," None",[32,266,173],{"class":58},[32,268,264],{"class":140},[32,270,179],{"class":62},[32,272,274],{"class":34,"line":273},18,[32,275,276],{"class":62},") -> Callable[[Callable[P, R]], Callable[P, R]]:\n",[32,278,280],{"class":34,"line":279},19,[32,281,282],{"class":97},"    \"\"\"\n",[32,284,286],{"class":34,"line":285},20,[32,287,288],{"class":97},"    Retry with exponential backoff + jitter.\n",[32,290,292],{"class":34,"line":291},21,[32,293,52],{"emptyLinePlaceholder":51},[32,295,297],{"class":34,"line":296},22,[32,298,299],{"class":97},"    Args:\n",[32,301,303],{"class":34,"line":302},23,[32,304,305],{"class":97},"        max_attempts: Total call attempts (including the first).\n",[32,307,309],{"class":34,"line":308},24,[32,310,311],{"class":97},"        base_delay: Initial delay in seconds; doubles each retry.\n",[32,313,315],{"class":34,"line":314},25,[32,316,317],{"class":97},"        max_delay: Cap on backoff delay.\n",[32,319,321],{"class":34,"line":320},26,[32,322,323],{"class":97},"        exceptions: Only these exception types trigger a retry.\n",[32,325,327],{"class":34,"line":326},27,[32,328,329],{"class":97},"        on_retry: Optional callback(exc, attempt) for metrics\u002Flogging.\n",[32,331,333],{"class":34,"line":332},28,[32,334,282],{"class":97},[32,336,338,341,344],{"class":34,"line":337},29,[32,339,340],{"class":58},"    def",[32,342,343],{"class":157}," decorator",[32,345,346],{"class":62},"(func: Callable[P, R]) -> Callable[P, R]:\n",[32,348,350,353,356],{"class":34,"line":349},30,[32,351,352],{"class":157},"        @functools.wraps",[32,354,355],{"class":62},"(func)          ",[32,357,358],{"class":38},"# preserves __name__, __doc__, __wrapped__, __annotations__\n",[32,360,362,365,368,371,374,377,380],{"class":34,"line":361},31,[32,363,364],{"class":58},"        def",[32,366,367],{"class":157}," wrapper",[32,369,370],{"class":62},"(",[32,372,373],{"class":58},"*",[32,375,376],{"class":62},"args: P.args, ",[32,378,379],{"class":58},"**",[32,381,382],{"class":62},"kwargs: P.kwargs) -> R:\n",[32,384,386,389,391,394,396,398],{"class":34,"line":385},32,[32,387,388],{"class":62},"            last_exc: ",[32,390,219],{"class":140},[32,392,393],{"class":58}," |",[32,395,264],{"class":140},[32,397,173],{"class":58},[32,399,400],{"class":140}," None\n",[32,402,404,407,410,413,416,418,421,424,427,430],{"class":34,"line":403},33,[32,405,406],{"class":58},"            for",[32,408,409],{"class":62}," attempt ",[32,411,412],{"class":58},"in",[32,414,415],{"class":140}," range",[32,417,370],{"class":62},[32,419,420],{"class":140},"1",[32,422,423],{"class":62},", max_attempts ",[32,425,426],{"class":58},"+",[32,428,429],{"class":140}," 1",[32,431,432],{"class":62},"):\n",[32,434,436,439],{"class":34,"line":435},34,[32,437,438],{"class":58},"                try",[32,440,441],{"class":62},":\n",[32,443,445,448,451,453,456,458],{"class":34,"line":444},35,[32,446,447],{"class":58},"                    return",[32,449,450],{"class":62}," func(",[32,452,373],{"class":58},[32,454,455],{"class":62},"args, ",[32,457,379],{"class":58},[32,459,460],{"class":62},"kwargs)\n",[32,462,464,467,470,473],{"class":34,"line":463},36,[32,465,466],{"class":58},"                except",[32,468,469],{"class":62}," exceptions ",[32,471,472],{"class":58},"as",[32,474,475],{"class":62}," exc:\n",[32,477,479,482,484],{"class":34,"line":478},37,[32,480,481],{"class":62},"                    last_exc ",[32,483,91],{"class":58},[32,485,486],{"class":62}," exc\n",[32,488,490,493,495,498],{"class":34,"line":489},38,[32,491,492],{"class":58},"                    if",[32,494,409],{"class":62},[32,496,497],{"class":58},">=",[32,499,500],{"class":62}," max_attempts:\n",[32,502,504,507,510,513,516,519,522,524,526,529,531],{"class":34,"line":503},39,[32,505,506],{"class":62},"                        log.error(",[32,508,509],{"class":97},"\"",[32,511,512],{"class":140},"%s",[32,514,515],{"class":97}," failed after ",[32,517,518],{"class":140},"%d",[32,520,521],{"class":97}," attempts: ",[32,523,512],{"class":140},[32,525,509],{"class":97},[32,527,528],{"class":62},", func.",[32,530,141],{"class":140},[32,532,533],{"class":62},", attempt, exc)\n",[32,535,537,540],{"class":34,"line":536},40,[32,538,539],{"class":58},"                        raise",[32,541,542],{"class":38},"          # re-raise the last exception — caller sees the real failure\n",[32,544,546,549,551,554,557,559,561,564,567,570,573,575],{"class":34,"line":545},41,[32,547,548],{"class":62},"                    delay ",[32,550,91],{"class":58},[32,552,553],{"class":140}," min",[32,555,556],{"class":62},"(base_delay ",[32,558,373],{"class":58},[32,560,233],{"class":62},[32,562,563],{"class":140},"2",[32,565,566],{"class":58}," **",[32,568,569],{"class":62}," (attempt ",[32,571,572],{"class":58},"-",[32,574,429],{"class":140},[32,576,577],{"class":62},")), max_delay)\n",[32,579,581,583],{"class":34,"line":580},42,[32,582,492],{"class":58},[32,584,585],{"class":62}," on_retry:\n",[32,587,589],{"class":34,"line":588},43,[32,590,591],{"class":62},"                        on_retry(exc, attempt)\n",[32,593,595,598,600,602,604,606,609,612,615,617,619,621,623],{"class":34,"line":594},44,[32,596,597],{"class":62},"                    log.warning(",[32,599,509],{"class":97},[32,601,512],{"class":140},[32,603,409],{"class":97},[32,605,518],{"class":140},[32,607,608],{"class":97}," failed, retrying in ",[32,610,611],{"class":140},"%.2f",[32,613,614],{"class":97},"s: ",[32,616,512],{"class":140},[32,618,509],{"class":97},[32,620,528],{"class":62},[32,622,141],{"class":140},[32,624,625],{"class":62},", attempt, delay, exc)\n",[32,627,629],{"class":34,"line":628},45,[32,630,631],{"class":62},"                    time.sleep(delay)\n",[32,633,635,638,641],{"class":34,"line":634},46,[32,636,637],{"class":58},"            raise",[32,639,640],{"class":62}," last_exc  ",[32,642,643],{"class":38},"# type: ignore[misc] — unreachable, but satisfies type checker\n",[32,645,647,650],{"class":34,"line":646},47,[32,648,649],{"class":58},"        return",[32,651,652],{"class":62}," wrapper\n",[32,654,656,659],{"class":34,"line":655},48,[32,657,658],{"class":58},"    return",[32,660,661],{"class":62}," decorator\n",[32,663,665],{"class":34,"line":664},49,[32,666,52],{"emptyLinePlaceholder":51},[32,668,670],{"class":34,"line":669},50,[32,671,672],{"class":38},"# Usage — full type safety preserved through the decorator\n",[32,674,676,679,681,685,687,690,692,695,697,699,702,704,707,710,713,716,719,722,724,727,730,733,736,739,742,744,747,749,751],{"class":34,"line":675},51,[32,677,678],{"class":157},"@retry",[32,680,370],{"class":62},[32,682,684],{"class":683},"sCrzJ","max_attempts",[32,686,91],{"class":58},[32,688,689],{"class":140},"3",[32,691,249],{"class":62},[32,693,694],{"class":683},"exceptions",[32,696,91],{"class":58},[32,698,370],{"class":62},[32,700,701],{"class":140},"ConnectionError",[32,703,249],{"class":62},[32,705,706],{"class":140},"TimeoutError",[32,708,709],{"class":62},"), ",[32,711,712],{"class":683},"on_retry",[32,714,715],{"class":58},"=lambda",[32,717,718],{"class":62}," e, a: ",[32,720,721],{"class":140},"print",[32,723,370],{"class":62},[32,725,726],{"class":58},"f",[32,728,729],{"class":97},"\"retry ",[32,731,732],{"class":140},"{",[32,734,735],{"class":62},"a",[32,737,738],{"class":140},"}",[32,740,741],{"class":97},": ",[32,743,732],{"class":140},[32,745,746],{"class":62},"e",[32,748,738],{"class":140},[32,750,509],{"class":97},[32,752,753],{"class":62},"))\n",[32,755,757,759,762,765,767,770,773],{"class":34,"line":756},52,[32,758,154],{"class":58},[32,760,761],{"class":157}," fetch_user",[32,763,764],{"class":62},"(user_id: ",[32,766,170],{"class":140},[32,768,769],{"class":62},") -> ",[32,771,772],{"class":140},"dict",[32,774,441],{"class":62},[32,776,778],{"class":34,"line":777},53,[32,779,780],{"class":97},"    \"\"\"Fetch a user from the API — retries on network errors.\"\"\"\n",[32,782,784,787],{"class":34,"line":783},54,[32,785,786],{"class":58},"    import",[32,788,789],{"class":62}," random\n",[32,791,793,796,799,802,805],{"class":34,"line":792},55,[32,794,795],{"class":58},"    if",[32,797,798],{"class":62}," random.random() ",[32,800,801],{"class":58},"\u003C",[32,803,804],{"class":140}," 0.5",[32,806,441],{"class":62},[32,808,810,813,816,818,821],{"class":34,"line":809},56,[32,811,812],{"class":58},"        raise",[32,814,815],{"class":140}," ConnectionError",[32,817,370],{"class":62},[32,819,820],{"class":97},"\"network timeout\"",[32,822,121],{"class":62},[32,824,826,828,831,834,837,840,842,845],{"class":34,"line":825},57,[32,827,658],{"class":58},[32,829,830],{"class":62}," {",[32,832,833],{"class":97},"\"id\"",[32,835,836],{"class":62},": user_id, ",[32,838,839],{"class":97},"\"name\"",[32,841,741],{"class":62},[32,843,844],{"class":97},"\"Ada\"",[32,846,847],{"class":62},"}\n",[32,849,851],{"class":34,"line":850},58,[32,852,52],{"emptyLinePlaceholder":51},[32,854,856],{"class":34,"line":855},59,[32,857,858],{"class":38},"# The wrapped function retains the original signature — inspect sees fetch_user, not `wrapper`\n",[32,860,862,864],{"class":34,"line":861},60,[32,863,59],{"class":58},[32,865,866],{"class":62}," inspect\n",[32,868,870,872,875],{"class":34,"line":869},61,[32,871,721],{"class":140},[32,873,874],{"class":62},"(inspect.signature(fetch_user))   ",[32,876,877],{"class":38},"# (user_id: int) -> dict — preserved by @functools.wraps\n",[14,879,881],{"id":880},"positional-keyword-and-default-arguments","Positional, Keyword, and Default Arguments",[19,883,884],{"language":21},[23,885,887],{"className":25,"code":886,"language":21,"meta":27,"style":27},"def create_user(name, role=\"member\", active=True):\n    return {\"name\": name, \"role\": role, \"active\": active}\n\nprint(create_user(\"Ada\"))                          # positional only\nprint(create_user(\"Grace\", role=\"admin\"))            # mix positional + keyword\nprint(create_user(name=\"Alan\", active=False))          # all keyword, any order\nprint(create_user(\"Linus\", \"admin\", False))              # all positional\n",[29,888,889,914,937,941,956,981,1011],{"__ignoreMap":27},[32,890,891,893,896,899,901,904,907,909,912],{"class":34,"line":35},[32,892,154],{"class":58},[32,894,895],{"class":157}," create_user",[32,897,898],{"class":62},"(name, role",[32,900,91],{"class":58},[32,902,903],{"class":97},"\"member\"",[32,905,906],{"class":62},", active",[32,908,91],{"class":58},[32,910,911],{"class":140},"True",[32,913,432],{"class":62},[32,915,916,918,920,922,925,928,931,934],{"class":34,"line":42},[32,917,658],{"class":58},[32,919,830],{"class":62},[32,921,839],{"class":97},[32,923,924],{"class":62},": name, ",[32,926,927],{"class":97},"\"role\"",[32,929,930],{"class":62},": role, ",[32,932,933],{"class":97},"\"active\"",[32,935,936],{"class":62},": active}\n",[32,938,939],{"class":34,"line":48},[32,940,52],{"emptyLinePlaceholder":51},[32,942,943,945,948,950,953],{"class":34,"line":55},[32,944,721],{"class":140},[32,946,947],{"class":62},"(create_user(",[32,949,844],{"class":97},[32,951,952],{"class":62},"))                          ",[32,954,955],{"class":38},"# positional only\n",[32,957,958,960,962,965,967,970,972,975,978],{"class":34,"line":66},[32,959,721],{"class":140},[32,961,947],{"class":62},[32,963,964],{"class":97},"\"Grace\"",[32,966,249],{"class":62},[32,968,969],{"class":683},"role",[32,971,91],{"class":58},[32,973,974],{"class":97},"\"admin\"",[32,976,977],{"class":62},"))            ",[32,979,980],{"class":38},"# mix positional + keyword\n",[32,982,983,985,987,990,992,995,997,1000,1002,1005,1008],{"class":34,"line":80},[32,984,721],{"class":140},[32,986,947],{"class":62},[32,988,989],{"class":683},"name",[32,991,91],{"class":58},[32,993,994],{"class":97},"\"Alan\"",[32,996,249],{"class":62},[32,998,999],{"class":683},"active",[32,1001,91],{"class":58},[32,1003,1004],{"class":140},"False",[32,1006,1007],{"class":62},"))          ",[32,1009,1010],{"class":38},"# all keyword, any order\n",[32,1012,1013,1015,1017,1020,1022,1024,1026,1028,1031],{"class":34,"line":85},[32,1014,721],{"class":140},[32,1016,947],{"class":62},[32,1018,1019],{"class":97},"\"Linus\"",[32,1021,249],{"class":62},[32,1023,974],{"class":97},[32,1025,249],{"class":62},[32,1027,1004],{"class":140},[32,1029,1030],{"class":62},"))              ",[32,1032,1033],{"class":38},"# all positional\n",[1035,1036,1038],"h3",{"id":1037},"positional-only-and-keyword-only-parameters","Positional-only and keyword-only parameters",[19,1040,1041],{"language":21},[23,1042,1044],{"className":25,"code":1043,"language":21,"meta":27,"style":27},"def divide(a, b, \u002F, *, precision=2):\n    # `\u002F` marks everything before it as positional-only\n    # `*` marks everything after it as keyword-only\n    return round(a \u002F b, precision)\n\nprint(divide(10, 3))                 # OK — a, b positional\nprint(divide(10, 3, precision=4))     # OK — precision is keyword-only\n# divide(a=10, b=3)                  # TypeError — a, b are positional-only\n# divide(10, 3, 4)                    # TypeError — precision must be passed by keyword\n",[29,1045,1046,1072,1077,1082,1097,1101,1121,1149,1154],{"__ignoreMap":27},[32,1047,1048,1050,1053,1056,1059,1061,1063,1066,1068,1070],{"class":34,"line":35},[32,1049,154],{"class":58},[32,1051,1052],{"class":157}," divide",[32,1054,1055],{"class":62},"(a, b, ",[32,1057,1058],{"class":58},"\u002F",[32,1060,249],{"class":62},[32,1062,373],{"class":58},[32,1064,1065],{"class":62},", precision",[32,1067,91],{"class":58},[32,1069,563],{"class":140},[32,1071,432],{"class":62},[32,1073,1074],{"class":34,"line":42},[32,1075,1076],{"class":38},"    # `\u002F` marks everything before it as positional-only\n",[32,1078,1079],{"class":34,"line":48},[32,1080,1081],{"class":38},"    # `*` marks everything after it as keyword-only\n",[32,1083,1084,1086,1089,1092,1094],{"class":34,"line":55},[32,1085,658],{"class":58},[32,1087,1088],{"class":140}," round",[32,1090,1091],{"class":62},"(a ",[32,1093,1058],{"class":58},[32,1095,1096],{"class":62}," b, precision)\n",[32,1098,1099],{"class":34,"line":66},[32,1100,52],{"emptyLinePlaceholder":51},[32,1102,1103,1105,1108,1111,1113,1115,1118],{"class":34,"line":80},[32,1104,721],{"class":140},[32,1106,1107],{"class":62},"(divide(",[32,1109,1110],{"class":140},"10",[32,1112,249],{"class":62},[32,1114,689],{"class":140},[32,1116,1117],{"class":62},"))                 ",[32,1119,1120],{"class":38},"# OK — a, b positional\n",[32,1122,1123,1125,1127,1129,1131,1133,1135,1138,1140,1143,1146],{"class":34,"line":85},[32,1124,721],{"class":140},[32,1126,1107],{"class":62},[32,1128,1110],{"class":140},[32,1130,249],{"class":62},[32,1132,689],{"class":140},[32,1134,249],{"class":62},[32,1136,1137],{"class":683},"precision",[32,1139,91],{"class":58},[32,1141,1142],{"class":140},"4",[32,1144,1145],{"class":62},"))     ",[32,1147,1148],{"class":38},"# OK — precision is keyword-only\n",[32,1150,1151],{"class":34,"line":107},[32,1152,1153],{"class":38},"# divide(a=10, b=3)                  # TypeError — a, b are positional-only\n",[32,1155,1156],{"class":34,"line":124},[32,1157,1158],{"class":38},"# divide(10, 3, 4)                    # TypeError — precision must be passed by keyword\n",[1160,1161,1162,1163,1165],"p",{},"Positional-only parameters (",[29,1164,1058],{},", since 3.8) let library authors rename internal parameter names later without breaking callers who pass by position — a real API-stability concern in widely-used libraries.",[14,1167,1169],{"id":1168},"the-mutable-default-argument-trap","The Mutable Default Argument Trap",[1160,1171,1172,1173],{},"This is Python's single most famous gotcha, and it exists because ",[1174,1175,1176],"strong",{},"default argument values are evaluated exactly ONCE, at function definition time — not on every call.",[19,1178,1179],{"language":21},[23,1180,1182],{"className":25,"code":1181,"language":21,"meta":27,"style":27},"# WRONG — the beginner's version\ndef add_item(item, cart=[]):\n    cart.append(item)\n    return cart\n\ncart1 = add_item(\"apple\")\nprint(cart1)                 # ['apple']\ncart2 = add_item(\"banana\")\nprint(cart2)                  # ['apple', 'banana']  \u003C- BUG: apple leaked in!\nprint(cart1 is cart2)           # True — they're literally the SAME list object\n",[29,1183,1184,1189,1204,1209,1216,1220,1235,1245,1259,1275],{"__ignoreMap":27},[32,1185,1186],{"class":34,"line":35},[32,1187,1188],{"class":38},"# WRONG — the beginner's version\n",[32,1190,1191,1193,1196,1199,1201],{"class":34,"line":42},[32,1192,154],{"class":58},[32,1194,1195],{"class":157}," add_item",[32,1197,1198],{"class":62},"(item, cart",[32,1200,91],{"class":58},[32,1202,1203],{"class":62},"[]):\n",[32,1205,1206],{"class":34,"line":48},[32,1207,1208],{"class":62},"    cart.append(item)\n",[32,1210,1211,1213],{"class":34,"line":55},[32,1212,658],{"class":58},[32,1214,1215],{"class":62}," cart\n",[32,1217,1218],{"class":34,"line":66},[32,1219,52],{"emptyLinePlaceholder":51},[32,1221,1222,1225,1227,1230,1233],{"class":34,"line":80},[32,1223,1224],{"class":62},"cart1 ",[32,1226,91],{"class":58},[32,1228,1229],{"class":62}," add_item(",[32,1231,1232],{"class":97},"\"apple\"",[32,1234,121],{"class":62},[32,1236,1237,1239,1242],{"class":34,"line":85},[32,1238,721],{"class":140},[32,1240,1241],{"class":62},"(cart1)                 ",[32,1243,1244],{"class":38},"# ['apple']\n",[32,1246,1247,1250,1252,1254,1257],{"class":34,"line":107},[32,1248,1249],{"class":62},"cart2 ",[32,1251,91],{"class":58},[32,1253,1229],{"class":62},[32,1255,1256],{"class":97},"\"banana\"",[32,1258,121],{"class":62},[32,1260,1261,1263,1266,1269,1272],{"class":34,"line":124},[32,1262,721],{"class":140},[32,1264,1265],{"class":62},"(cart2)                  ",[32,1267,1268],{"class":38},"# ['apple', 'banana']  \u003C- ",[32,1270,1271],{"class":58},"BUG",[32,1273,1274],{"class":38},": apple leaked in!\n",[32,1276,1277,1279,1282,1285,1288],{"class":34,"line":129},[32,1278,721],{"class":140},[32,1280,1281],{"class":62},"(cart1 ",[32,1283,1284],{"class":58},"is",[32,1286,1287],{"class":62}," cart2)           ",[32,1289,1290],{"class":38},"# True — they're literally the SAME list object\n",[1160,1292,1293,741,1296,1299,1300,1303,1304,1306,1307,1310,1311,1314,1315,1319,1320,1323],{},[1174,1294,1295],{},"The mechanism",[29,1297,1298],{},"def add_item(item, cart=[]):"," creates the empty list ",[29,1301,1302],{},"[]"," exactly once, when the ",[29,1305,154],{}," statement executes (i.e., when the module is imported\u002Floaded), and stores it as part of the function object itself (",[29,1308,1309],{},"add_item.__defaults__","). Every call that doesn't explicitly pass ",[29,1312,1313],{},"cart"," reuses that ",[1316,1317,1318],"em",{},"same"," list object. Because lists are mutable, ",[29,1321,1322],{},".append()"," mutates the shared default in place, and the mutation persists across calls.",[19,1325,1326],{"language":21},[23,1327,1329],{"className":25,"code":1328,"language":21,"meta":27,"style":27},"# RIGHT — use None as a sentinel default, create the mutable object inside\ndef add_item(item, cart=None):\n    if cart is None:\n        cart = []\n    cart.append(item)\n    return cart\n\ncart1 = add_item(\"apple\")\nprint(cart1)              # ['apple']\ncart2 = add_item(\"banana\")\nprint(cart2)                # ['banana']  — correct, independent lists\nprint(cart1 is cart2)          # False\n",[29,1330,1331,1336,1350,1363,1373,1377,1383,1387,1399,1408,1420,1430],{"__ignoreMap":27},[32,1332,1333],{"class":34,"line":35},[32,1334,1335],{"class":38},"# RIGHT — use None as a sentinel default, create the mutable object inside\n",[32,1337,1338,1340,1342,1344,1346,1348],{"class":34,"line":42},[32,1339,154],{"class":58},[32,1341,1195],{"class":157},[32,1343,1198],{"class":62},[32,1345,91],{"class":58},[32,1347,256],{"class":140},[32,1349,432],{"class":62},[32,1351,1352,1354,1357,1359,1361],{"class":34,"line":48},[32,1353,795],{"class":58},[32,1355,1356],{"class":62}," cart ",[32,1358,1284],{"class":58},[32,1360,264],{"class":140},[32,1362,441],{"class":62},[32,1364,1365,1368,1370],{"class":34,"line":55},[32,1366,1367],{"class":62},"        cart ",[32,1369,91],{"class":58},[32,1371,1372],{"class":62}," []\n",[32,1374,1375],{"class":34,"line":66},[32,1376,1208],{"class":62},[32,1378,1379,1381],{"class":34,"line":80},[32,1380,658],{"class":58},[32,1382,1215],{"class":62},[32,1384,1385],{"class":34,"line":85},[32,1386,52],{"emptyLinePlaceholder":51},[32,1388,1389,1391,1393,1395,1397],{"class":34,"line":107},[32,1390,1224],{"class":62},[32,1392,91],{"class":58},[32,1394,1229],{"class":62},[32,1396,1232],{"class":97},[32,1398,121],{"class":62},[32,1400,1401,1403,1406],{"class":34,"line":124},[32,1402,721],{"class":140},[32,1404,1405],{"class":62},"(cart1)              ",[32,1407,1244],{"class":38},[32,1409,1410,1412,1414,1416,1418],{"class":34,"line":129},[32,1411,1249],{"class":62},[32,1413,91],{"class":58},[32,1415,1229],{"class":62},[32,1417,1256],{"class":97},[32,1419,121],{"class":62},[32,1421,1422,1424,1427],{"class":34,"line":146},[32,1423,721],{"class":140},[32,1425,1426],{"class":62},"(cart2)                ",[32,1428,1429],{"class":38},"# ['banana']  — correct, independent lists\n",[32,1431,1432,1434,1436,1438,1441],{"class":34,"line":151},[32,1433,721],{"class":140},[32,1435,1281],{"class":62},[32,1437,1284],{"class":58},[32,1439,1440],{"class":62}," cart2)          ",[32,1442,1443],{"class":38},"# False\n",[19,1445,1446],{"language":21},[23,1447,1449],{"className":25,"code":1448,"language":21,"meta":27,"style":27},"# You can inspect the trap directly:\nprint(add_item.__defaults__)   # (None,) — the sentinel, not a shared list\n",[29,1450,1451,1456],{"__ignoreMap":27},[32,1452,1453],{"class":34,"line":35},[32,1454,1455],{"class":38},"# You can inspect the trap directly:\n",[32,1457,1458,1460,1463,1466,1469],{"class":34,"line":42},[32,1459,721],{"class":140},[32,1461,1462],{"class":62},"(add_item.",[32,1464,1465],{"class":140},"__defaults__",[32,1467,1468],{"class":62},")   ",[32,1470,1471],{"class":38},"# (None,) — the sentinel, not a shared list\n",[1160,1473,1474,1475,1478,1479,1481],{},"This gotcha applies to ",[1174,1476,1477],{},"any mutable default",": lists, dicts, sets, and custom mutable objects. Immutable defaults (",[29,1480,256],{},", numbers, strings, tuples) are completely safe because they can't be mutated in place — there's nothing to leak.",[14,1483,1485,1488,1489],{"id":1484},"args-and-kwargs",[29,1486,1487],{},"*args"," and ",[29,1490,1491],{},"**kwargs",[19,1493,1494],{"language":21},[23,1495,1497],{"className":25,"code":1496,"language":21,"meta":27,"style":27},"def summarize(*args, **kwargs):\n    print(\"positional:\", args)      # a tuple\n    print(\"keyword:\", kwargs)         # a dict\n\nsummarize(1, 2, 3, name=\"Ada\", role=\"admin\")\n# positional: (1, 2, 3)\n# keyword: {'name': 'Ada', 'role': 'admin'}\n",[29,1498,1499,1517,1533,1548,1552,1585,1590],{"__ignoreMap":27},[32,1500,1501,1503,1506,1508,1510,1512,1514],{"class":34,"line":35},[32,1502,154],{"class":58},[32,1504,1505],{"class":157}," summarize",[32,1507,370],{"class":62},[32,1509,373],{"class":58},[32,1511,455],{"class":62},[32,1513,379],{"class":58},[32,1515,1516],{"class":62},"kwargs):\n",[32,1518,1519,1522,1524,1527,1530],{"class":34,"line":42},[32,1520,1521],{"class":140},"    print",[32,1523,370],{"class":62},[32,1525,1526],{"class":97},"\"positional:\"",[32,1528,1529],{"class":62},", args)      ",[32,1531,1532],{"class":38},"# a tuple\n",[32,1534,1535,1537,1539,1542,1545],{"class":34,"line":48},[32,1536,1521],{"class":140},[32,1538,370],{"class":62},[32,1540,1541],{"class":97},"\"keyword:\"",[32,1543,1544],{"class":62},", kwargs)         ",[32,1546,1547],{"class":38},"# a dict\n",[32,1549,1550],{"class":34,"line":55},[32,1551,52],{"emptyLinePlaceholder":51},[32,1553,1554,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575,1577,1579,1581,1583],{"class":34,"line":66},[32,1555,1556],{"class":62},"summarize(",[32,1558,420],{"class":140},[32,1560,249],{"class":62},[32,1562,563],{"class":140},[32,1564,249],{"class":62},[32,1566,689],{"class":140},[32,1568,249],{"class":62},[32,1570,989],{"class":683},[32,1572,91],{"class":58},[32,1574,844],{"class":97},[32,1576,249],{"class":62},[32,1578,969],{"class":683},[32,1580,91],{"class":58},[32,1582,974],{"class":97},[32,1584,121],{"class":62},[32,1586,1587],{"class":34,"line":80},[32,1588,1589],{"class":38},"# positional: (1, 2, 3)\n",[32,1591,1592],{"class":34,"line":85},[32,1593,1594],{"class":38},"# keyword: {'name': 'Ada', 'role': 'admin'}\n",[1035,1596,1598],{"id":1597},"real-world-use-transparent-wrapperproxy-functions","Real-world use: transparent wrapper\u002Fproxy functions",[19,1600,1601],{"language":21},[23,1602,1604],{"className":25,"code":1603,"language":21,"meta":27,"style":27},"import logging\nimport time\n\ndef timed(func):\n    def wrapper(*args, **kwargs):\n        start = time.perf_counter()\n        result = func(*args, **kwargs)     # forward EVERYTHING, whatever the signature\n        elapsed = time.perf_counter() - start\n        logging.info(f\"{func.__name__} took {elapsed:.4f}s\")\n        return result\n    return wrapper\n\n@timed\ndef fetch_report(user_id, *, include_archived=False):\n    return {\"user_id\": user_id, \"archived\": include_archived}\n\nfetch_report(42, include_archived=True)\n",[29,1605,1606,1613,1620,1624,1634,1650,1660,1681,1696,1731,1738,1744,1748,1753,1774,1791,1795],{"__ignoreMap":27},[32,1607,1608,1610],{"class":34,"line":35},[32,1609,59],{"class":58},[32,1611,1612],{"class":62}," logging\n",[32,1614,1615,1617],{"class":34,"line":42},[32,1616,59],{"class":58},[32,1618,1619],{"class":62}," time\n",[32,1621,1622],{"class":34,"line":48},[32,1623,52],{"emptyLinePlaceholder":51},[32,1625,1626,1628,1631],{"class":34,"line":55},[32,1627,154],{"class":58},[32,1629,1630],{"class":157}," timed",[32,1632,1633],{"class":62},"(func):\n",[32,1635,1636,1638,1640,1642,1644,1646,1648],{"class":34,"line":66},[32,1637,340],{"class":58},[32,1639,367],{"class":157},[32,1641,370],{"class":62},[32,1643,373],{"class":58},[32,1645,455],{"class":62},[32,1647,379],{"class":58},[32,1649,1516],{"class":62},[32,1651,1652,1655,1657],{"class":34,"line":80},[32,1653,1654],{"class":62},"        start ",[32,1656,91],{"class":58},[32,1658,1659],{"class":62}," time.perf_counter()\n",[32,1661,1662,1665,1667,1669,1671,1673,1675,1678],{"class":34,"line":85},[32,1663,1664],{"class":62},"        result ",[32,1666,91],{"class":58},[32,1668,450],{"class":62},[32,1670,373],{"class":58},[32,1672,455],{"class":62},[32,1674,379],{"class":58},[32,1676,1677],{"class":62},"kwargs)     ",[32,1679,1680],{"class":38},"# forward EVERYTHING, whatever the signature\n",[32,1682,1683,1686,1688,1691,1693],{"class":34,"line":107},[32,1684,1685],{"class":62},"        elapsed ",[32,1687,91],{"class":58},[32,1689,1690],{"class":62}," time.perf_counter() ",[32,1692,572],{"class":58},[32,1694,1695],{"class":62}," start\n",[32,1697,1698,1701,1703,1705,1707,1710,1713,1716,1718,1721,1724,1726,1729],{"class":34,"line":124},[32,1699,1700],{"class":62},"        logging.info(",[32,1702,726],{"class":58},[32,1704,509],{"class":97},[32,1706,732],{"class":140},[32,1708,1709],{"class":62},"func.",[32,1711,1712],{"class":140},"__name__}",[32,1714,1715],{"class":97}," took ",[32,1717,732],{"class":140},[32,1719,1720],{"class":62},"elapsed",[32,1722,1723],{"class":58},":.4f",[32,1725,738],{"class":140},[32,1727,1728],{"class":97},"s\"",[32,1730,121],{"class":62},[32,1732,1733,1735],{"class":34,"line":129},[32,1734,649],{"class":58},[32,1736,1737],{"class":62}," result\n",[32,1739,1740,1742],{"class":34,"line":146},[32,1741,658],{"class":58},[32,1743,652],{"class":62},[32,1745,1746],{"class":34,"line":151},[32,1747,52],{"emptyLinePlaceholder":51},[32,1749,1750],{"class":34,"line":164},[32,1751,1752],{"class":157},"@timed\n",[32,1754,1755,1757,1760,1763,1765,1768,1770,1772],{"class":34,"line":182},[32,1756,154],{"class":58},[32,1758,1759],{"class":157}," fetch_report",[32,1761,1762],{"class":62},"(user_id, ",[32,1764,373],{"class":58},[32,1766,1767],{"class":62},", include_archived",[32,1769,91],{"class":58},[32,1771,1004],{"class":140},[32,1773,432],{"class":62},[32,1775,1776,1778,1780,1783,1785,1788],{"class":34,"line":198},[32,1777,658],{"class":58},[32,1779,830],{"class":62},[32,1781,1782],{"class":97},"\"user_id\"",[32,1784,836],{"class":62},[32,1786,1787],{"class":97},"\"archived\"",[32,1789,1790],{"class":62},": include_archived}\n",[32,1792,1793],{"class":34,"line":213},[32,1794,52],{"emptyLinePlaceholder":51},[32,1796,1797,1800,1803,1805,1808,1810,1812],{"class":34,"line":241},[32,1798,1799],{"class":62},"fetch_report(",[32,1801,1802],{"class":140},"42",[32,1804,249],{"class":62},[32,1806,1807],{"class":683},"include_archived",[32,1809,91],{"class":58},[32,1811,911],{"class":140},[32,1813,121],{"class":62},[1035,1815,1817,1818,1488,1820,1822],{"id":1816},"unpacking-with-and-at-the-call-site","Unpacking with ",[29,1819,373],{},[29,1821,379],{}," at the call site",[19,1824,1825],{"language":21},[23,1826,1828],{"className":25,"code":1827,"language":21,"meta":27,"style":27},"def move(x, y, z):\n    return f\"Moving to ({x}, {y}, {z})\"\n\ncoords = [1, 2, 3]\nprint(move(*coords))          # unpacks list -> move(1, 2, 3)\n\nparams = {\"x\": 1, \"y\": 2, \"z\": 3}\nprint(move(**params))           # unpacks dict -> move(x=1, y=2, z=3)\n\n# Merging dicts (3.5+) uses the same ** syntax\ndefaults = {\"role\": \"member\", \"active\": True}\noverrides = {\"role\": \"admin\"}\nmerged = {**defaults, **overrides}   # {'role': 'admin', 'active': True}\n",[29,1829,1830,1840,1878,1882,1905,1920,1924,1960,1974,1978,1983,2008,2025],{"__ignoreMap":27},[32,1831,1832,1834,1837],{"class":34,"line":35},[32,1833,154],{"class":58},[32,1835,1836],{"class":157}," move",[32,1838,1839],{"class":62},"(x, y, z):\n",[32,1841,1842,1844,1847,1850,1852,1855,1857,1859,1861,1864,1866,1868,1870,1873,1875],{"class":34,"line":42},[32,1843,658],{"class":58},[32,1845,1846],{"class":58}," f",[32,1848,1849],{"class":97},"\"Moving to (",[32,1851,732],{"class":140},[32,1853,1854],{"class":62},"x",[32,1856,738],{"class":140},[32,1858,249],{"class":97},[32,1860,732],{"class":140},[32,1862,1863],{"class":62},"y",[32,1865,738],{"class":140},[32,1867,249],{"class":97},[32,1869,732],{"class":140},[32,1871,1872],{"class":62},"z",[32,1874,738],{"class":140},[32,1876,1877],{"class":97},")\"\n",[32,1879,1880],{"class":34,"line":48},[32,1881,52],{"emptyLinePlaceholder":51},[32,1883,1884,1887,1889,1892,1894,1896,1898,1900,1902],{"class":34,"line":55},[32,1885,1886],{"class":62},"coords ",[32,1888,91],{"class":58},[32,1890,1891],{"class":62}," [",[32,1893,420],{"class":140},[32,1895,249],{"class":62},[32,1897,563],{"class":140},[32,1899,249],{"class":62},[32,1901,689],{"class":140},[32,1903,1904],{"class":62},"]\n",[32,1906,1907,1909,1912,1914,1917],{"class":34,"line":66},[32,1908,721],{"class":140},[32,1910,1911],{"class":62},"(move(",[32,1913,373],{"class":58},[32,1915,1916],{"class":62},"coords))          ",[32,1918,1919],{"class":38},"# unpacks list -> move(1, 2, 3)\n",[32,1921,1922],{"class":34,"line":80},[32,1923,52],{"emptyLinePlaceholder":51},[32,1925,1926,1929,1931,1933,1936,1938,1940,1942,1945,1947,1949,1951,1954,1956,1958],{"class":34,"line":85},[32,1927,1928],{"class":62},"params ",[32,1930,91],{"class":58},[32,1932,830],{"class":62},[32,1934,1935],{"class":97},"\"x\"",[32,1937,741],{"class":62},[32,1939,420],{"class":140},[32,1941,249],{"class":62},[32,1943,1944],{"class":97},"\"y\"",[32,1946,741],{"class":62},[32,1948,563],{"class":140},[32,1950,249],{"class":62},[32,1952,1953],{"class":97},"\"z\"",[32,1955,741],{"class":62},[32,1957,689],{"class":140},[32,1959,847],{"class":62},[32,1961,1962,1964,1966,1968,1971],{"class":34,"line":107},[32,1963,721],{"class":140},[32,1965,1911],{"class":62},[32,1967,379],{"class":58},[32,1969,1970],{"class":62},"params))           ",[32,1972,1973],{"class":38},"# unpacks dict -> move(x=1, y=2, z=3)\n",[32,1975,1976],{"class":34,"line":124},[32,1977,52],{"emptyLinePlaceholder":51},[32,1979,1980],{"class":34,"line":129},[32,1981,1982],{"class":38},"# Merging dicts (3.5+) uses the same ** syntax\n",[32,1984,1985,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006],{"class":34,"line":146},[32,1986,1987],{"class":62},"defaults ",[32,1989,91],{"class":58},[32,1991,830],{"class":62},[32,1993,927],{"class":97},[32,1995,741],{"class":62},[32,1997,903],{"class":97},[32,1999,249],{"class":62},[32,2001,933],{"class":97},[32,2003,741],{"class":62},[32,2005,911],{"class":140},[32,2007,847],{"class":62},[32,2009,2010,2013,2015,2017,2019,2021,2023],{"class":34,"line":151},[32,2011,2012],{"class":62},"overrides ",[32,2014,91],{"class":58},[32,2016,830],{"class":62},[32,2018,927],{"class":97},[32,2020,741],{"class":62},[32,2022,974],{"class":97},[32,2024,847],{"class":62},[32,2026,2027,2030,2032,2034,2036,2039,2041,2044],{"class":34,"line":164},[32,2028,2029],{"class":62},"merged ",[32,2031,91],{"class":58},[32,2033,830],{"class":62},[32,2035,379],{"class":58},[32,2037,2038],{"class":62},"defaults, ",[32,2040,379],{"class":58},[32,2042,2043],{"class":62},"overrides}   ",[32,2045,2046],{"class":38},"# {'role': 'admin', 'active': True}\n",[1035,2048,2050],{"id":2049},"argument-order-rules","Argument order rules",[1160,2052,2053,2054,2056,2057,2059],{},"When combining all forms, the required order is: standard positional\u002Fkeyword params, then ",[29,2055,1487],{},", then keyword-only params, then ",[29,2058,1491],{},".",[19,2061,2062],{"language":21},[23,2063,2065],{"className":25,"code":2064,"language":21,"meta":27,"style":27},"def full_signature(pos_only, \u002F, standard, *args, kw_only, **kwargs):\n    return pos_only, standard, args, kw_only, kwargs\n\nprint(full_signature(1, 2, 3, 4, kw_only=5, extra=6))\n# (1, 2, (3, 4), 5, {'extra': 6})\n",[29,2066,2067,2091,2098,2102,2145],{"__ignoreMap":27},[32,2068,2069,2071,2074,2077,2079,2082,2084,2087,2089],{"class":34,"line":35},[32,2070,154],{"class":58},[32,2072,2073],{"class":157}," full_signature",[32,2075,2076],{"class":62},"(pos_only, ",[32,2078,1058],{"class":58},[32,2080,2081],{"class":62},", standard, ",[32,2083,373],{"class":58},[32,2085,2086],{"class":62},"args, kw_only, ",[32,2088,379],{"class":58},[32,2090,1516],{"class":62},[32,2092,2093,2095],{"class":34,"line":42},[32,2094,658],{"class":58},[32,2096,2097],{"class":62}," pos_only, standard, args, kw_only, kwargs\n",[32,2099,2100],{"class":34,"line":48},[32,2101,52],{"emptyLinePlaceholder":51},[32,2103,2104,2106,2109,2111,2113,2115,2117,2119,2121,2123,2125,2128,2130,2133,2135,2138,2140,2143],{"class":34,"line":55},[32,2105,721],{"class":140},[32,2107,2108],{"class":62},"(full_signature(",[32,2110,420],{"class":140},[32,2112,249],{"class":62},[32,2114,563],{"class":140},[32,2116,249],{"class":62},[32,2118,689],{"class":140},[32,2120,249],{"class":62},[32,2122,1142],{"class":140},[32,2124,249],{"class":62},[32,2126,2127],{"class":683},"kw_only",[32,2129,91],{"class":58},[32,2131,2132],{"class":140},"5",[32,2134,249],{"class":62},[32,2136,2137],{"class":683},"extra",[32,2139,91],{"class":58},[32,2141,2142],{"class":140},"6",[32,2144,753],{"class":62},[32,2146,2147],{"class":34,"line":66},[32,2148,2149],{"class":38},"# (1, 2, (3, 4), 5, {'extra': 6})\n",[14,2151,2153],{"id":2152},"type-annotations-preview-full-coverage-in-chapter-20","Type Annotations (Preview — Full Coverage in Chapter 20)",[19,2155,2156],{"language":21},[23,2157,2159],{"className":25,"code":2158,"language":21,"meta":27,"style":27},"def calculate_discount(price: float, percent: float = 10.0) -> float:\n    return price * (1 - percent \u002F 100)\n\nprint(calculate_discount(100.0))          # 90.0\nprint(calculate_discount(100.0, 25))        # 75.0\n",[29,2160,2161,2188,2214,2218,2233],{"__ignoreMap":27},[32,2162,2163,2165,2168,2171,2173,2176,2178,2180,2182,2184,2186],{"class":34,"line":35},[32,2164,154],{"class":58},[32,2166,2167],{"class":157}," calculate_discount",[32,2169,2170],{"class":62},"(price: ",[32,2172,188],{"class":140},[32,2174,2175],{"class":62},", percent: ",[32,2177,188],{"class":140},[32,2179,173],{"class":58},[32,2181,208],{"class":140},[32,2183,769],{"class":62},[32,2185,188],{"class":140},[32,2187,441],{"class":62},[32,2189,2190,2192,2195,2197,2199,2201,2204,2207,2209,2212],{"class":34,"line":42},[32,2191,658],{"class":58},[32,2193,2194],{"class":62}," price ",[32,2196,373],{"class":58},[32,2198,233],{"class":62},[32,2200,420],{"class":140},[32,2202,2203],{"class":58}," -",[32,2205,2206],{"class":62}," percent ",[32,2208,1058],{"class":58},[32,2210,2211],{"class":140}," 100",[32,2213,121],{"class":62},[32,2215,2216],{"class":34,"line":48},[32,2217,52],{"emptyLinePlaceholder":51},[32,2219,2220,2222,2225,2228,2230],{"class":34,"line":55},[32,2221,721],{"class":140},[32,2223,2224],{"class":62},"(calculate_discount(",[32,2226,2227],{"class":140},"100.0",[32,2229,1007],{"class":62},[32,2231,2232],{"class":38},"# 90.0\n",[32,2234,2235,2237,2239,2241,2243,2246,2249],{"class":34,"line":66},[32,2236,721],{"class":140},[32,2238,2224],{"class":62},[32,2240,2227],{"class":140},[32,2242,249],{"class":62},[32,2244,2245],{"class":140},"25",[32,2247,2248],{"class":62},"))        ",[32,2250,2251],{"class":38},"# 75.0\n",[1160,2253,2254,2257,2258,2261,2262,2059],{},[1174,2255,2256],{},"Critical",": annotations are ",[1174,2259,2260],{},"not enforced at runtime by the interpreter",". They're metadata for humans, IDEs, and external tools like ",[29,2263,2264],{},"mypy",[19,2266,2267],{"language":21},[23,2268,2270],{"className":25,"code":2269,"language":21,"meta":27,"style":27},"def add(a: int, b: int) -> int:\n    return a + b\n\nprint(add(\"hello\", \"world\"))   # \"helloworld\" — NO error, annotations are NOT checked!\nprint(add.__annotations__)      # {'a': \u003Cclass 'int'>, 'b': \u003Cclass 'int'>, 'return': \u003Cclass 'int'>}\n",[29,2271,2272,2295,2307,2311,2332],{"__ignoreMap":27},[32,2273,2274,2276,2279,2282,2284,2287,2289,2291,2293],{"class":34,"line":35},[32,2275,154],{"class":58},[32,2277,2278],{"class":157}," add",[32,2280,2281],{"class":62},"(a: ",[32,2283,170],{"class":140},[32,2285,2286],{"class":62},", b: ",[32,2288,170],{"class":140},[32,2290,769],{"class":62},[32,2292,170],{"class":140},[32,2294,441],{"class":62},[32,2296,2297,2299,2302,2304],{"class":34,"line":42},[32,2298,658],{"class":58},[32,2300,2301],{"class":62}," a ",[32,2303,426],{"class":58},[32,2305,2306],{"class":62}," b\n",[32,2308,2309],{"class":34,"line":48},[32,2310,52],{"emptyLinePlaceholder":51},[32,2312,2313,2315,2318,2321,2323,2326,2329],{"class":34,"line":55},[32,2314,721],{"class":140},[32,2316,2317],{"class":62},"(add(",[32,2319,2320],{"class":97},"\"hello\"",[32,2322,249],{"class":62},[32,2324,2325],{"class":97},"\"world\"",[32,2327,2328],{"class":62},"))   ",[32,2330,2331],{"class":38},"# \"helloworld\" — NO error, annotations are NOT checked!\n",[32,2333,2334,2336,2339,2342,2344],{"class":34,"line":66},[32,2335,721],{"class":140},[32,2337,2338],{"class":62},"(add.",[32,2340,2341],{"class":140},"__annotations__",[32,2343,101],{"class":62},[32,2345,2346],{"class":38},"# {'a': \u003Cclass 'int'>, 'b': \u003Cclass 'int'>, 'return': \u003Cclass 'int'>}\n",[14,2348,2350],{"id":2349},"docstrings","Docstrings",[19,2352,2353],{"language":21},[23,2354,2356],{"className":25,"code":2355,"language":21,"meta":27,"style":27},"def calculate_bmi(weight_kg: float, height_m: float) -> float:\n    \"\"\"Calculate Body Mass Index.\n\n    Args:\n        weight_kg: Body weight in kilograms.\n        height_m: Height in meters.\n\n    Returns:\n        BMI as weight_kg \u002F height_m ** 2.\n\n    Raises:\n        ValueError: If height_m is not positive.\n    \"\"\"\n    if height_m \u003C= 0:\n        raise ValueError(\"height_m must be positive\")\n    return weight_kg \u002F height_m ** 2\n",[29,2357,2358,2381,2386,2390,2394,2399,2404,2408,2413,2418,2422,2427,2432,2436,2451,2465],{"__ignoreMap":27},[32,2359,2360,2362,2365,2368,2370,2373,2375,2377,2379],{"class":34,"line":35},[32,2361,154],{"class":58},[32,2363,2364],{"class":157}," calculate_bmi",[32,2366,2367],{"class":62},"(weight_kg: ",[32,2369,188],{"class":140},[32,2371,2372],{"class":62},", height_m: ",[32,2374,188],{"class":140},[32,2376,769],{"class":62},[32,2378,188],{"class":140},[32,2380,441],{"class":62},[32,2382,2383],{"class":34,"line":42},[32,2384,2385],{"class":97},"    \"\"\"Calculate Body Mass Index.\n",[32,2387,2388],{"class":34,"line":48},[32,2389,52],{"emptyLinePlaceholder":51},[32,2391,2392],{"class":34,"line":55},[32,2393,299],{"class":97},[32,2395,2396],{"class":34,"line":66},[32,2397,2398],{"class":97},"        weight_kg: Body weight in kilograms.\n",[32,2400,2401],{"class":34,"line":80},[32,2402,2403],{"class":97},"        height_m: Height in meters.\n",[32,2405,2406],{"class":34,"line":85},[32,2407,52],{"emptyLinePlaceholder":51},[32,2409,2410],{"class":34,"line":107},[32,2411,2412],{"class":97},"    Returns:\n",[32,2414,2415],{"class":34,"line":124},[32,2416,2417],{"class":97},"        BMI as weight_kg \u002F height_m ** 2.\n",[32,2419,2420],{"class":34,"line":129},[32,2421,52],{"emptyLinePlaceholder":51},[32,2423,2424],{"class":34,"line":146},[32,2425,2426],{"class":97},"    Raises:\n",[32,2428,2429],{"class":34,"line":151},[32,2430,2431],{"class":97},"        ValueError: If height_m is not positive.\n",[32,2433,2434],{"class":34,"line":164},[32,2435,282],{"class":97},[32,2437,2438,2440,2443,2446,2449],{"class":34,"line":182},[32,2439,795],{"class":58},[32,2441,2442],{"class":62}," height_m ",[32,2444,2445],{"class":58},"\u003C=",[32,2447,2448],{"class":140}," 0",[32,2450,441],{"class":62},[32,2452,2453,2455,2458,2460,2463],{"class":34,"line":198},[32,2454,812],{"class":58},[32,2456,2457],{"class":140}," ValueError",[32,2459,370],{"class":62},[32,2461,2462],{"class":97},"\"height_m must be positive\"",[32,2464,121],{"class":62},[32,2466,2467,2469,2472,2474,2476,2478],{"class":34,"line":213},[32,2468,658],{"class":58},[32,2470,2471],{"class":62}," weight_kg ",[32,2473,1058],{"class":58},[32,2475,2442],{"class":62},[32,2477,379],{"class":58},[32,2479,2480],{"class":140}," 2\n",[1160,2482,2483,2484,2487,2488,2491,2492,2495],{},"This Google-style docstring format is widely adopted (also common: NumPy-style and reStructuredText\u002FSphinx-style). Tools like ",[29,2485,2486],{},"pydoc",", Sphinx, and IDEs render ",[29,2489,2490],{},"__doc__"," directly — ",[29,2493,2494],{},"help(calculate_bmi)"," prints it in the REPL.",[14,2497,2499],{"id":2498},"closures-over-loop-variables-the-late-binding-trap","Closures Over Loop Variables — The Late-Binding Trap",[1160,2501,2502,2503,2506,2507,2510],{},"Closures capture ",[1174,2504,2505],{},"variables by reference to their enclosing scope, not by value at definition time",". Combined with the fact that Python's ",[29,2508,2509],{},"for"," loop variable is a single reused name (not a fresh binding per iteration, unlike some other languages), this produces one of Python's most common real-world bugs.",[19,2512,2513],{"language":21},[23,2514,2516],{"className":25,"code":2515,"language":21,"meta":27,"style":27},"# WRONG — every closure captures the SAME variable `i`, evaluated LATE (at call time)\nfuncs = []\nfor i in range(3):\n    funcs.append(lambda: i)\n\nprint([f() for f in funcs])   # [2, 2, 2] — NOT [0, 1, 2]!\n",[29,2517,2518,2523,2532,2549,2560,2564],{"__ignoreMap":27},[32,2519,2520],{"class":34,"line":35},[32,2521,2522],{"class":38},"# WRONG — every closure captures the SAME variable `i`, evaluated LATE (at call time)\n",[32,2524,2525,2528,2530],{"class":34,"line":42},[32,2526,2527],{"class":62},"funcs ",[32,2529,91],{"class":58},[32,2531,1372],{"class":62},[32,2533,2534,2536,2539,2541,2543,2545,2547],{"class":34,"line":48},[32,2535,2509],{"class":58},[32,2537,2538],{"class":62}," i ",[32,2540,412],{"class":58},[32,2542,415],{"class":140},[32,2544,370],{"class":62},[32,2546,689],{"class":140},[32,2548,432],{"class":62},[32,2550,2551,2554,2557],{"class":34,"line":55},[32,2552,2553],{"class":62},"    funcs.append(",[32,2555,2556],{"class":58},"lambda",[32,2558,2559],{"class":62},": i)\n",[32,2561,2562],{"class":34,"line":66},[32,2563,52],{"emptyLinePlaceholder":51},[32,2565,2566,2568,2571,2573,2576,2578,2581],{"class":34,"line":80},[32,2567,721],{"class":140},[32,2569,2570],{"class":62},"([f() ",[32,2572,2509],{"class":58},[32,2574,2575],{"class":62}," f ",[32,2577,412],{"class":58},[32,2579,2580],{"class":62}," funcs])   ",[32,2582,2583],{"class":38},"# [2, 2, 2] — NOT [0, 1, 2]!\n",[1160,2585,2586,2588,2589,2592,2593,2596,2597,2600,2601,2604,2605,2607,2608,2610,2611,2613],{},[1174,2587,1295],{},": the ",[29,2590,2591],{},"lambda: i"," doesn't capture the ",[1316,2594,2595],{},"value"," of ",[29,2598,2599],{},"i"," at the moment the lambda is created — it captures the ",[1316,2602,2603],{},"variable"," ",[29,2606,2599],{}," itself (a reference to the enclosing scope's cell). By the time any of the lambdas are actually called, the loop has finished and ",[29,2609,2599],{}," holds its final value, ",[29,2612,563],{},". All three lambdas look up the same cell and see the same final value.",[19,2615,2616],{"language":21},[23,2617,2619],{"className":25,"code":2618,"language":21,"meta":27,"style":27},"# RIGHT — force early binding via a default argument\n# (default argument values ARE evaluated once, at def time — see the trap above,\n#  here that \"gotcha\" becomes the FIX)\nfuncs = []\nfor i in range(3):\n    funcs.append(lambda i=i: i)   # i=i evaluates the CURRENT i, binds it as a default\n\nprint([f() for f in funcs])   # [0, 1, 2] — correct\n",[29,2620,2621,2626,2631,2636,2644,2660,2677,2681],{"__ignoreMap":27},[32,2622,2623],{"class":34,"line":35},[32,2624,2625],{"class":38},"# RIGHT — force early binding via a default argument\n",[32,2627,2628],{"class":34,"line":42},[32,2629,2630],{"class":38},"# (default argument values ARE evaluated once, at def time — see the trap above,\n",[32,2632,2633],{"class":34,"line":48},[32,2634,2635],{"class":38},"#  here that \"gotcha\" becomes the FIX)\n",[32,2637,2638,2640,2642],{"class":34,"line":55},[32,2639,2527],{"class":62},[32,2641,91],{"class":58},[32,2643,1372],{"class":62},[32,2645,2646,2648,2650,2652,2654,2656,2658],{"class":34,"line":66},[32,2647,2509],{"class":58},[32,2649,2538],{"class":62},[32,2651,412],{"class":58},[32,2653,415],{"class":140},[32,2655,370],{"class":62},[32,2657,689],{"class":140},[32,2659,432],{"class":62},[32,2661,2662,2664,2666,2669,2671,2674],{"class":34,"line":80},[32,2663,2553],{"class":62},[32,2665,2556],{"class":58},[32,2667,2668],{"class":62}," i",[32,2670,91],{"class":58},[32,2672,2673],{"class":62},"i: i)   ",[32,2675,2676],{"class":38},"# i=i evaluates the CURRENT i, binds it as a default\n",[32,2678,2679],{"class":34,"line":85},[32,2680,52],{"emptyLinePlaceholder":51},[32,2682,2683,2685,2687,2689,2691,2693,2695],{"class":34,"line":107},[32,2684,721],{"class":140},[32,2686,2570],{"class":62},[32,2688,2509],{"class":58},[32,2690,2575],{"class":62},[32,2692,412],{"class":58},[32,2694,2580],{"class":62},[32,2696,2697],{"class":38},"# [0, 1, 2] — correct\n",[19,2699,2700],{"language":21},[23,2701,2703],{"className":25,"code":2702,"language":21,"meta":27,"style":27},"# ALTERNATIVE — a factory function creates a genuinely new scope per call\ndef make_getter(value):\n    return lambda: value\n\nfuncs = [make_getter(i) for i in range(3)]\nprint([f() for f in funcs])   # [0, 1, 2] — correct, each call creates a fresh `value`\n",[29,2704,2705,2710,2720,2730,2734,2758],{"__ignoreMap":27},[32,2706,2707],{"class":34,"line":35},[32,2708,2709],{"class":38},"# ALTERNATIVE — a factory function creates a genuinely new scope per call\n",[32,2711,2712,2714,2717],{"class":34,"line":42},[32,2713,154],{"class":58},[32,2715,2716],{"class":157}," make_getter",[32,2718,2719],{"class":62},"(value):\n",[32,2721,2722,2724,2727],{"class":34,"line":48},[32,2723,658],{"class":58},[32,2725,2726],{"class":58}," lambda",[32,2728,2729],{"class":62},": value\n",[32,2731,2732],{"class":34,"line":55},[32,2733,52],{"emptyLinePlaceholder":51},[32,2735,2736,2738,2740,2743,2745,2747,2749,2751,2753,2755],{"class":34,"line":66},[32,2737,2527],{"class":62},[32,2739,91],{"class":58},[32,2741,2742],{"class":62}," [make_getter(i) ",[32,2744,2509],{"class":58},[32,2746,2538],{"class":62},[32,2748,412],{"class":58},[32,2750,415],{"class":140},[32,2752,370],{"class":62},[32,2754,689],{"class":140},[32,2756,2757],{"class":62},")]\n",[32,2759,2760,2762,2764,2766,2768,2770,2772],{"class":34,"line":80},[32,2761,721],{"class":140},[32,2763,2570],{"class":62},[32,2765,2509],{"class":58},[32,2767,2575],{"class":62},[32,2769,412],{"class":58},[32,2771,2580],{"class":62},[32,2773,2774],{"class":38},"# [0, 1, 2] — correct, each call creates a fresh `value`\n",[1160,2776,2777],{},"This exact bug commonly appears in real code building lists of callbacks (event handlers, button click handlers, deferred tasks queued in a loop) — anywhere a closure is created inside a loop and called later.",[14,2779,2781],{"id":2780},"recursion","Recursion",[19,2783,2784],{"language":21},[23,2785,2787],{"className":25,"code":2786,"language":21,"meta":27,"style":27},"def factorial(n):\n    if n \u003C= 1:\n        return 1\n    return n * factorial(n - 1)\n\nprint(factorial(10))   # 3628800\n",[29,2788,2789,2799,2812,2819,2836,2840],{"__ignoreMap":27},[32,2790,2791,2793,2796],{"class":34,"line":35},[32,2792,154],{"class":58},[32,2794,2795],{"class":157}," factorial",[32,2797,2798],{"class":62},"(n):\n",[32,2800,2801,2803,2806,2808,2810],{"class":34,"line":42},[32,2802,795],{"class":58},[32,2804,2805],{"class":62}," n ",[32,2807,2445],{"class":58},[32,2809,429],{"class":140},[32,2811,441],{"class":62},[32,2813,2814,2816],{"class":34,"line":48},[32,2815,649],{"class":58},[32,2817,2818],{"class":140}," 1\n",[32,2820,2821,2823,2825,2827,2830,2832,2834],{"class":34,"line":55},[32,2822,658],{"class":58},[32,2824,2805],{"class":62},[32,2826,373],{"class":58},[32,2828,2829],{"class":62}," factorial(n ",[32,2831,572],{"class":58},[32,2833,429],{"class":140},[32,2835,121],{"class":62},[32,2837,2838],{"class":34,"line":66},[32,2839,52],{"emptyLinePlaceholder":51},[32,2841,2842,2844,2847,2849,2851],{"class":34,"line":80},[32,2843,721],{"class":140},[32,2845,2846],{"class":62},"(factorial(",[32,2848,1110],{"class":140},[32,2850,2328],{"class":62},[32,2852,2853],{"class":38},"# 3628800\n",[1160,2855,2856,2857,2860,2861,2864],{},"Python has ",[1174,2858,2859],{},"no tail-call optimization",", unlike Scheme or some functional languages — deep recursion consumes real stack frames and hits ",[29,2862,2863],{},"sys.getrecursionlimit()"," (default 1000).",[19,2866,2867],{"language":21},[23,2868,2870],{"className":25,"code":2869,"language":21,"meta":27,"style":27},"import sys\n\ndef count_down(n):\n    if n \u003C= 0:\n        return\n    count_down(n - 1)\n\nprint(sys.getrecursionlimit())   # 1000\ntry:\n    count_down(10_000)\nexcept RecursionError as e:\n    print(f\"Blew the stack: {e}\")\n",[29,2871,2872,2879,2883,2892,2904,2909,2920,2924,2934,2941,2951,2965],{"__ignoreMap":27},[32,2873,2874,2876],{"class":34,"line":35},[32,2875,59],{"class":58},[32,2877,2878],{"class":62}," sys\n",[32,2880,2881],{"class":34,"line":42},[32,2882,52],{"emptyLinePlaceholder":51},[32,2884,2885,2887,2890],{"class":34,"line":48},[32,2886,154],{"class":58},[32,2888,2889],{"class":157}," count_down",[32,2891,2798],{"class":62},[32,2893,2894,2896,2898,2900,2902],{"class":34,"line":55},[32,2895,795],{"class":58},[32,2897,2805],{"class":62},[32,2899,2445],{"class":58},[32,2901,2448],{"class":140},[32,2903,441],{"class":62},[32,2905,2906],{"class":34,"line":66},[32,2907,2908],{"class":58},"        return\n",[32,2910,2911,2914,2916,2918],{"class":34,"line":80},[32,2912,2913],{"class":62},"    count_down(n ",[32,2915,572],{"class":58},[32,2917,429],{"class":140},[32,2919,121],{"class":62},[32,2921,2922],{"class":34,"line":85},[32,2923,52],{"emptyLinePlaceholder":51},[32,2925,2926,2928,2931],{"class":34,"line":107},[32,2927,721],{"class":140},[32,2929,2930],{"class":62},"(sys.getrecursionlimit())   ",[32,2932,2933],{"class":38},"# 1000\n",[32,2935,2936,2939],{"class":34,"line":124},[32,2937,2938],{"class":58},"try",[32,2940,441],{"class":62},[32,2942,2943,2946,2949],{"class":34,"line":129},[32,2944,2945],{"class":62},"    count_down(",[32,2947,2948],{"class":140},"10_000",[32,2950,121],{"class":62},[32,2952,2953,2956,2959,2962],{"class":34,"line":146},[32,2954,2955],{"class":58},"except",[32,2957,2958],{"class":140}," RecursionError",[32,2960,2961],{"class":58}," as",[32,2963,2964],{"class":62}," e:\n",[32,2966,2967,2969,2971,2973,2976,2978,2980,2982,2984],{"class":34,"line":151},[32,2968,1521],{"class":140},[32,2970,370],{"class":62},[32,2972,726],{"class":58},[32,2974,2975],{"class":97},"\"Blew the stack: ",[32,2977,732],{"class":140},[32,2979,746],{"class":62},[32,2981,738],{"class":140},[32,2983,509],{"class":97},[32,2985,121],{"class":62},[1160,2987,2988,2991,2992,2995],{},[1174,2989,2990],{},"Best practice",": convert deep recursion to an explicit loop with a stack\u002Fqueue data structure for any code that might process user-controlled or unbounded-depth input (tree traversal on untrusted data, recursive descent parsers on adversarial input). Don't just raise ",[29,2993,2994],{},"sys.setrecursionlimit()"," — that risks a genuine C-level stack overflow (segfault) rather than a clean Python exception, since Python's recursion limit is a soft guard around the underlying C call stack.",[14,2997,2999],{"id":2998},"tips-tricks","💡 Tips & Tricks",[3001,3002,3003,3017,3034,3044,3056],"ul",{},[3004,3005,3006,3012,3013,3016],"li",{},[1174,3007,3008,3011],{},[29,3009,3010],{},"functools.lru_cache"," memoizes recursive functions for free"," — ",[29,3014,3015],{},"@functools.lru_cache(maxsize=None)"," on a pure recursive function (like naive Fibonacci) turns exponential time into linear time with one line — covered fully in chapter 10.",[3004,3018,3019,3012,3026,3029,3030,3033],{},[1174,3020,3021,3023,3024],{},[29,3022,373],{}," alone (no name) forces keyword-only arguments without collecting ",[29,3025,1491],{},[29,3027,3028],{},"def f(a, b, *, c):"," makes ",[29,3031,3032],{},"c"," keyword-only without needing to accept arbitrary extra keywords.",[3004,3035,3036,3043],{},[1174,3037,3038,3039,3042],{},"Use ",[29,3040,3041],{},"inspect.signature()"," to introspect a function's parameters at runtime"," — useful for building decorators or CLI argument parsers that need to validate calls against a function's real signature.",[3004,3045,3046,3012,3049,249,3052,3055],{},[1174,3047,3048],{},"Docstrings are runtime-accessible data, not just comments",[29,3050,3051],{},"func.__doc__",[29,3053,3054],{},"help(func)",", and Sphinx's autodoc all read the same string; write them like documentation, not like comments.",[3004,3057,3058,3069,3070,3072],{},[1174,3059,3060,3063,3064,3066,3067],{},[29,3061,3062],{},"return"," with no value and no ",[29,3065,3062],{}," statement at all both return ",[29,3068,256],{}," — but an explicit bare ",[29,3071,3062],{}," is idiomatic for \"exit early,\" communicating intent more clearly than letting control fall off the end of the function.",[14,3074,3076],{"id":3075},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3001,3078,3079,3091,3104,3120,3151],{},[3004,3080,3081,3087,3088,3090],{},[1174,3082,3083,3084,3086],{},"Mutable default arguments are evaluated once at ",[29,3085,154],{}," time and shared across every call that doesn't override them"," — the single most common real-world Python bug in this list; always default mutable arguments to ",[29,3089,256],{}," and construct the real object inside the function body.",[3004,3092,3093,3096,3097,3099,3100,3103],{},[1174,3094,3095],{},"Closures in loops capture variables late (by reference), not values at creation time"," — a list of lambdas\u002Ffunctions built inside a ",[29,3098,2509],{}," loop will all see the loop variable's ",[1316,3101,3102],{},"final"," value unless you force early binding via a default-argument trick or a factory function.",[3004,3105,3106,3112,3113,3115,3116,3119],{},[1174,3107,3108,3111],{},[29,3109,3110],{},"RecursionError"," is a soft limit, not a memory guarantee"," — Python's ",[29,3114,2863],{}," protects the ",[1316,3117,3118],{},"interpreter's"," C stack, but raising the limit arbitrarily high can still crash the process with a real segfault before Python's own check fires, especially in C-extension-heavy call stacks.",[3004,3121,3122,3012,3125,3128,3129,3132,3133,1058,3135,3138,3139,3142,3143,3146,3147,3150],{},[1174,3123,3124],{},"Annotations are not runtime type checks",[29,3126,3127],{},"def f(x: int)"," does not stop you from calling ",[29,3130,3131],{},"f(\"a string\")","; nothing raises unless you separately run ",[29,3134,2264],{},[29,3136,3137],{},"pyright"," or explicitly validate types yourself (e.g., with ",[29,3140,3141],{},"pydantic"," or manual ",[29,3144,3145],{},"isinstance"," checks) — this trips up developers coming from statically-typed languages who expect a ",[29,3148,3149],{},"TypeError"," at the call site.",[3004,3152,3153,3012,3161,3164,3165,3168,3169,3171,3172,3175,3176,3012,3179,3181],{},[1174,3154,3155,3156,1058,3158,3160],{},"Argument-order rules for ",[29,3157,1487],{},[29,3159,1491],{},"\u002Fkeyword-only params are strict and easy to get backwards",[29,3162,3163],{},"def f(a, *args, b, **kwargs)"," is valid (keyword-only ",[29,3166,3167],{},"b"," after ",[29,3170,1487],{},"); ",[29,3173,3174],{},"def f(a, **kwargs, b)"," is a ",[29,3177,3178],{},"SyntaxError",[29,3180,1491],{}," must always be last.",[14,3183,3185],{"id":3184},"spot-the-bug","🧠 Spot the Bug",[1160,3187,3188],{},"A junior developer writes a logging helper. What goes wrong after the app has been running for a while?",[19,3190,3191],{"language":21},[23,3192,3194],{"className":25,"code":3193,"language":21,"meta":27,"style":27},"def log_event(message, history=[]):\n    history.append(message)\n    if len(history) > 3:\n        print(f\"Recent events: {history[-3:]}\")\n    return history\n\nlog_event(\"user login\")\nlog_event(\"page view\")\nlog_event(\"user login\")     # what does the \"Recent events\" list contain over time?\nlog_event(\"page view\")\n",[29,3195,3196,3210,3215,3232,3262,3269,3273,3283,3292,3304],{"__ignoreMap":27},[32,3197,3198,3200,3203,3206,3208],{"class":34,"line":35},[32,3199,154],{"class":58},[32,3201,3202],{"class":157}," log_event",[32,3204,3205],{"class":62},"(message, history",[32,3207,91],{"class":58},[32,3209,1203],{"class":62},[32,3211,3212],{"class":34,"line":42},[32,3213,3214],{"class":62},"    history.append(message)\n",[32,3216,3217,3219,3222,3225,3228,3230],{"class":34,"line":48},[32,3218,795],{"class":58},[32,3220,3221],{"class":140}," len",[32,3223,3224],{"class":62},"(history) ",[32,3226,3227],{"class":58},">",[32,3229,176],{"class":140},[32,3231,441],{"class":62},[32,3233,3234,3237,3239,3241,3244,3246,3249,3251,3253,3256,3258,3260],{"class":34,"line":55},[32,3235,3236],{"class":140},"        print",[32,3238,370],{"class":62},[32,3240,726],{"class":58},[32,3242,3243],{"class":97},"\"Recent events: ",[32,3245,732],{"class":140},[32,3247,3248],{"class":62},"history[",[32,3250,572],{"class":58},[32,3252,689],{"class":140},[32,3254,3255],{"class":62},":]",[32,3257,738],{"class":140},[32,3259,509],{"class":97},[32,3261,121],{"class":62},[32,3263,3264,3266],{"class":34,"line":66},[32,3265,658],{"class":58},[32,3267,3268],{"class":62}," history\n",[32,3270,3271],{"class":34,"line":80},[32,3272,52],{"emptyLinePlaceholder":51},[32,3274,3275,3278,3281],{"class":34,"line":85},[32,3276,3277],{"class":62},"log_event(",[32,3279,3280],{"class":97},"\"user login\"",[32,3282,121],{"class":62},[32,3284,3285,3287,3290],{"class":34,"line":107},[32,3286,3277],{"class":62},[32,3288,3289],{"class":97},"\"page view\"",[32,3291,121],{"class":62},[32,3293,3294,3296,3298,3301],{"class":34,"line":124},[32,3295,3277],{"class":62},[32,3297,3280],{"class":97},[32,3299,3300],{"class":62},")     ",[32,3302,3303],{"class":38},"# what does the \"Recent events\" list contain over time?\n",[32,3305,3306,3308,3310],{"class":34,"line":129},[32,3307,3277],{"class":62},[32,3309,3289],{"class":97},[32,3311,121],{"class":62},[3313,3314,3315,3319,3344,3350],"details",{},[3316,3317,3318],"summary",{},"Answer",[1160,3320,3321,3324,3325,3328,3329,3332,3333,3336,3337,3339,3340,3343],{},[29,3322,3323],{},"history=[]"," is a single list object created once when ",[29,3326,3327],{},"log_event"," is defined, shared across ",[1174,3330,3331],{},"every call"," to the function for the lifetime of the program — not just within one \"session.\" Every call without an explicit ",[29,3334,3335],{},"history"," argument appends to that same list forever, so ",[29,3338,3335],{}," grows unboundedly across unrelated calls (a memory leak in long-running processes like a web server), and any code that assumes each call starts with a fresh, empty history is simply wrong. Worse, if two different parts of a codebase call ",[29,3341,3342],{},"log_event(msg)"," expecting independent histories, they silently share state.",[1160,3345,3346,3347,2059],{},"The fix: ",[29,3348,3349],{},"def log_event(message, history=None): history = [] if history is None else history",[1160,3351,3352,3355],{},[1174,3353,3354],{},"The lesson",": mutable default arguments are a footgun specifically because Python function objects are created once and persist for the process lifetime — their defaults are ordinary attributes on that long-lived object, not fresh values conjured per call.",[14,3357,3359],{"id":3358},"key-takeaways","Key Takeaways",[3001,3361,3362,3370,3388,3401,3409,3412],{},[3004,3363,3364,3365,3367,3368,2059],{},"Functions are first-class objects; a function without an explicit ",[29,3366,3062],{}," returns ",[29,3369,256],{},[3004,3371,3372,3373,3375,3376,249,3378,249,3381,3384,3385,3387],{},"Default argument values are evaluated exactly once, at ",[29,3374,154],{}," time — never use a mutable literal (",[29,3377,1302],{},[29,3379,3380],{},"{}",[29,3382,3383],{},"set()",") as a default; use ",[29,3386,256],{}," and construct inside the function body instead.",[3004,3389,3390,3392,3393,3395,3396,1058,3398,3400],{},[29,3391,1487],{}," collects extra positional arguments into a tuple; ",[29,3394,1491],{}," collects extra keyword arguments into a dict; ",[29,3397,373],{},[29,3399,1058],{}," in a signature enforce keyword-only\u002Fpositional-only parameters.",[3004,3402,3403,3404,1058,3406,3408],{},"Type annotations are documentation and tooling metadata only — the interpreter never enforces them at runtime; use ",[29,3405,2264],{},[29,3407,3137],{}," for static checking (chapter 20).",[3004,3410,3411],{},"Closures capture enclosing variables by reference and resolve them at call time, not definition time — loop variables captured by closures created inside a loop all see the loop's final value unless you force early binding.",[3004,3413,3414,3415,3417],{},"Python has no tail-call optimization; deep recursion hits a soft ",[29,3416,3110],{}," limit protecting the real C stack — convert unbounded recursion to iteration for untrusted or unbounded-depth input.",[3419,3420,3421],"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 .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 .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html .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":3423},[3424,3425,3428,3429,3436,3437,3438,3439,3440,3441,3442,3443],{"id":16,"depth":42,"text":17},{"id":880,"depth":42,"text":881,"children":3426},[3427],{"id":1037,"depth":48,"text":1038},{"id":1168,"depth":42,"text":1169},{"id":1484,"depth":42,"text":3430,"children":3431},"*args and **kwargs",[3432,3433,3435],{"id":1597,"depth":48,"text":1598},{"id":1816,"depth":48,"text":3434},"Unpacking with * and ** at the call site",{"id":2049,"depth":48,"text":2050},{"id":2152,"depth":42,"text":2153},{"id":2349,"depth":42,"text":2350},{"id":2498,"depth":42,"text":2499},{"id":2780,"depth":42,"text":2781},{"id":2998,"depth":42,"text":2999},{"id":3075,"depth":42,"text":3076},{"id":3184,"depth":42,"text":3185},{"id":3358,"depth":42,"text":3359},"md",{},"\u002Fpython\u002F05-functions",{"title":5,"description":27},"python\u002F05-functions","lpHrTuSScEopZ27lDS21SiIBqTSQqAqxl03_dBB79co",1789924651329]