[{"data":1,"prerenderedAt":4164},["ShallowReactive",2],{"page-\u002Fpython\u002F20-type-hints-and-typing":3},{"id":4,"title":5,"body":6,"description":27,"extension":4158,"meta":4159,"navigation":57,"path":4160,"seo":4161,"stem":4162,"__hash__":4163},"content\u002Fpython\u002F20-type-hints-and-typing.md","20 — Type Hints & Typing",{"type":7,"value":8,"toc":4134},"minimark",[9,13,18,811,1191,1195,1411,1435,1515,1526,1691,1716,1846,1854,2088,2094,2200,2205,2335,2349,2356,2520,2551,2634,2653,2659,2896,2909,2979,2992,3005,3150,3174,3180,3403,3416,3423,3464,3516,3530,3590,3599,3603,3683,3687,3774,3778,3784,3904,4062,4066,4130],[10,11,5],"h1",{"id":12},"_20-type-hints-typing",[14,15,17],"h2",{"id":16},"production-type-system-generics-protocols-and-api-boundaries","Production Type System — Generics, Protocols, and API Boundaries",[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","# ── Production: a fully typed repository pattern with generics and protocols ──\n# Demonstrates the real-world type system: TypeVar bounds, Protocol (structural typing),\n# TypedDict for JSON shapes, and overload for API ergonomics.\n\nfrom typing import (\n    Protocol, TypeVar, Generic, TypedDict, NotRequired,\n    overload, Callable, Final, TYPE_CHECKING,\n)\nfrom dataclasses import dataclass\nfrom abc import abstractmethod\n\n# ── Protocol: structural typing — \"if it has these methods, it qualifies\" ──\n# No inheritance required — any class with matching methods satisfies the Protocol.\nclass Repository(Protocol[T := TypeVar(\"T\", bound=\"Entity\")]):\n    \"\"\"Repository protocol — any class implementing these methods is a valid repo.\"\"\"\n    @abstractmethod\n    def get_by_id(self, id: int) -> T | None: ...\n    @abstractmethod\n    def save(self, entity: T) -> T: ...\n    @abstractmethod\n    def delete(self, id: int) -> bool: ...\n\n# ── TypedDict: typed JSON API shapes — validated by mypy, invisible at runtime ──\nclass UserAPIResponse(TypedDict):\n    id: int\n    email: str\n    name: str\n    roles: list[str]\n    metadata: NotRequired[dict[str, str]]   # 3.11+ — key may be absent entirely\n\nclass CreateUserRequest(TypedDict):\n    email: str\n    name: str\n    roles: NotRequired[list[str]]   # optional with default in the handler\n\n# ── Generic repository implementation ──\n@dataclass\nclass Entity:\n    id: int\n\n@dataclass\nclass User(Entity):\n    email: str\n    name: str\n\nclass InMemoryRepository(Generic[T]):\n    \"\"\"A generic in-memory repository — type-safe for any Entity subclass.\"\"\"\n    def __init__(self) -> None:\n        self._store: dict[int, T] = {}\n\n    def get_by_id(self, id: int) -> T | None:\n        return self._store.get(id)\n\n    def save(self, entity: T) -> T:\n        self._store[entity.id] = entity   # type: ignore[attr-defined] — Entity has .id\n        return entity\n\n    def delete(self, id: int) -> bool:\n        return self._store.pop(id, None) is not None\n\n    def find_all(self) -> list[T]:\n        return list(self._store.values())\n\n# ── Usage: mypy verifies the entire chain ──\nuser_repo: InMemoryRepository[User] = InMemoryRepository()\nuser_repo.save(User(id=1, email=\"ada@example.com\", name=\"Ada\"))\n\nfound = user_repo.get_by_id(1)   # mypy infers: User | None\nif found:\n    print(found.email)            # mypy knows `found` is User here (narrowed by `if`)\n\n# mypy catches type mismatches at the call site:\n# user_repo.save(\"not a user\")   # mypy error: Argument 1 has incompatible type \"str\"; expected \"User\"\n","",[29,30,31,40,46,52,59,76,82,95,101,114,127,132,138,144,177,183,189,219,224,237,242,264,269,275,292,303,312,320,332,350,355,369,376,383,396,401,407,413,424,433,438,443,458,465,472,477,488,494,510,529,534,553,570,575,585,601,609,614,631,659,664,675,691,696,702,713,749,754,773,782,794,799,805],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── Production: a fully typed repository pattern with generics and protocols ──\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"# Demonstrates the real-world type system: TypeVar bounds, Protocol (structural typing),\n",[32,47,49],{"class":34,"line":48},3,[32,50,51],{"class":38},"# TypedDict for JSON shapes, and overload for API ergonomics.\n",[32,53,55],{"class":34,"line":54},4,[32,56,58],{"emptyLinePlaceholder":57},true,"\n",[32,60,62,66,70,73],{"class":34,"line":61},5,[32,63,65],{"class":64},"svdQ7","from",[32,67,69],{"class":68},"ssxIu"," typing ",[32,71,72],{"class":64},"import",[32,74,75],{"class":68}," (\n",[32,77,79],{"class":34,"line":78},6,[32,80,81],{"class":68},"    Protocol, TypeVar, Generic, TypedDict, NotRequired,\n",[32,83,85,88,92],{"class":34,"line":84},7,[32,86,87],{"class":68},"    overload, Callable, Final, ",[32,89,91],{"class":90},"snvgF","TYPE_CHECKING",[32,93,94],{"class":68},",\n",[32,96,98],{"class":34,"line":97},8,[32,99,100],{"class":68},")\n",[32,102,104,106,109,111],{"class":34,"line":103},9,[32,105,65],{"class":64},[32,107,108],{"class":68}," dataclasses ",[32,110,72],{"class":64},[32,112,113],{"class":68}," dataclass\n",[32,115,117,119,122,124],{"class":34,"line":116},10,[32,118,65],{"class":64},[32,120,121],{"class":68}," abc ",[32,123,72],{"class":64},[32,125,126],{"class":68}," abstractmethod\n",[32,128,130],{"class":34,"line":129},11,[32,131,58],{"emptyLinePlaceholder":57},[32,133,135],{"class":34,"line":134},12,[32,136,137],{"class":38},"# ── Protocol: structural typing — \"if it has these methods, it qualifies\" ──\n",[32,139,141],{"class":34,"line":140},13,[32,142,143],{"class":38},"# No inheritance required — any class with matching methods satisfies the Protocol.\n",[32,145,147,150,154,157,161,164,168,171,174],{"class":34,"line":146},14,[32,148,149],{"class":64},"class",[32,151,153],{"class":152},"sIsaT"," Repository",[32,155,156],{"class":68},"(Protocol[T := TypeVar(",[32,158,160],{"class":159},"sJ6F3","\"T\"",[32,162,163],{"class":68},", ",[32,165,167],{"class":166},"sCrzJ","bound",[32,169,170],{"class":64},"=",[32,172,173],{"class":159},"\"Entity\"",[32,175,176],{"class":68},")]):\n",[32,178,180],{"class":34,"line":179},15,[32,181,182],{"class":159},"    \"\"\"Repository protocol — any class implementing these methods is a valid repo.\"\"\"\n",[32,184,186],{"class":34,"line":185},16,[32,187,188],{"class":152},"    @abstractmethod\n",[32,190,192,195,198,201,204,207,210,213,216],{"class":34,"line":191},17,[32,193,194],{"class":64},"    def",[32,196,197],{"class":152}," get_by_id",[32,199,200],{"class":68},"(self, id: ",[32,202,203],{"class":90},"int",[32,205,206],{"class":68},") -> T ",[32,208,209],{"class":64},"|",[32,211,212],{"class":90}," None",[32,214,215],{"class":68},": ",[32,217,218],{"class":90},"...\n",[32,220,222],{"class":34,"line":221},18,[32,223,188],{"class":152},[32,225,227,229,232,235],{"class":34,"line":226},19,[32,228,194],{"class":64},[32,230,231],{"class":152}," save",[32,233,234],{"class":68},"(self, entity: T) -> T: ",[32,236,218],{"class":90},[32,238,240],{"class":34,"line":239},20,[32,241,188],{"class":152},[32,243,245,247,250,252,254,257,260,262],{"class":34,"line":244},21,[32,246,194],{"class":64},[32,248,249],{"class":152}," delete",[32,251,200],{"class":68},[32,253,203],{"class":90},[32,255,256],{"class":68},") -> ",[32,258,259],{"class":90},"bool",[32,261,215],{"class":68},[32,263,218],{"class":90},[32,265,267],{"class":34,"line":266},22,[32,268,58],{"emptyLinePlaceholder":57},[32,270,272],{"class":34,"line":271},23,[32,273,274],{"class":38},"# ── TypedDict: typed JSON API shapes — validated by mypy, invisible at runtime ──\n",[32,276,278,280,283,286,289],{"class":34,"line":277},24,[32,279,149],{"class":64},[32,281,282],{"class":152}," UserAPIResponse",[32,284,285],{"class":68},"(",[32,287,288],{"class":152},"TypedDict",[32,290,291],{"class":68},"):\n",[32,293,295,298,300],{"class":34,"line":294},25,[32,296,297],{"class":90},"    id",[32,299,215],{"class":68},[32,301,302],{"class":90},"int\n",[32,304,306,309],{"class":34,"line":305},26,[32,307,308],{"class":68},"    email: ",[32,310,311],{"class":90},"str\n",[32,313,315,318],{"class":34,"line":314},27,[32,316,317],{"class":68},"    name: ",[32,319,311],{"class":90},[32,321,323,326,329],{"class":34,"line":322},28,[32,324,325],{"class":68},"    roles: list[",[32,327,328],{"class":90},"str",[32,330,331],{"class":68},"]\n",[32,333,335,338,340,342,344,347],{"class":34,"line":334},29,[32,336,337],{"class":68},"    metadata: NotRequired[dict[",[32,339,328],{"class":90},[32,341,163],{"class":68},[32,343,328],{"class":90},[32,345,346],{"class":68},"]]   ",[32,348,349],{"class":38},"# 3.11+ — key may be absent entirely\n",[32,351,353],{"class":34,"line":352},30,[32,354,58],{"emptyLinePlaceholder":57},[32,356,358,360,363,365,367],{"class":34,"line":357},31,[32,359,149],{"class":64},[32,361,362],{"class":152}," CreateUserRequest",[32,364,285],{"class":68},[32,366,288],{"class":152},[32,368,291],{"class":68},[32,370,372,374],{"class":34,"line":371},32,[32,373,308],{"class":68},[32,375,311],{"class":90},[32,377,379,381],{"class":34,"line":378},33,[32,380,317],{"class":68},[32,382,311],{"class":90},[32,384,386,389,391,393],{"class":34,"line":385},34,[32,387,388],{"class":68},"    roles: NotRequired[list[",[32,390,328],{"class":90},[32,392,346],{"class":68},[32,394,395],{"class":38},"# optional with default in the handler\n",[32,397,399],{"class":34,"line":398},35,[32,400,58],{"emptyLinePlaceholder":57},[32,402,404],{"class":34,"line":403},36,[32,405,406],{"class":38},"# ── Generic repository implementation ──\n",[32,408,410],{"class":34,"line":409},37,[32,411,412],{"class":152},"@dataclass\n",[32,414,416,418,421],{"class":34,"line":415},38,[32,417,149],{"class":64},[32,419,420],{"class":152}," Entity",[32,422,423],{"class":68},":\n",[32,425,427,429,431],{"class":34,"line":426},39,[32,428,297],{"class":90},[32,430,215],{"class":68},[32,432,302],{"class":90},[32,434,436],{"class":34,"line":435},40,[32,437,58],{"emptyLinePlaceholder":57},[32,439,441],{"class":34,"line":440},41,[32,442,412],{"class":152},[32,444,446,448,451,453,456],{"class":34,"line":445},42,[32,447,149],{"class":64},[32,449,450],{"class":152}," User",[32,452,285],{"class":68},[32,454,455],{"class":152},"Entity",[32,457,291],{"class":68},[32,459,461,463],{"class":34,"line":460},43,[32,462,308],{"class":68},[32,464,311],{"class":90},[32,466,468,470],{"class":34,"line":467},44,[32,469,317],{"class":68},[32,471,311],{"class":90},[32,473,475],{"class":34,"line":474},45,[32,476,58],{"emptyLinePlaceholder":57},[32,478,480,482,485],{"class":34,"line":479},46,[32,481,149],{"class":64},[32,483,484],{"class":152}," InMemoryRepository",[32,486,487],{"class":68},"(Generic[T]):\n",[32,489,491],{"class":34,"line":490},47,[32,492,493],{"class":159},"    \"\"\"A generic in-memory repository — type-safe for any Entity subclass.\"\"\"\n",[32,495,497,499,502,505,508],{"class":34,"line":496},48,[32,498,194],{"class":64},[32,500,501],{"class":90}," __init__",[32,503,504],{"class":68},"(self) -> ",[32,506,507],{"class":90},"None",[32,509,423],{"class":68},[32,511,513,516,519,521,524,526],{"class":34,"line":512},49,[32,514,515],{"class":90},"        self",[32,517,518],{"class":68},"._store: dict[",[32,520,203],{"class":90},[32,522,523],{"class":68},", T] ",[32,525,170],{"class":64},[32,527,528],{"class":68}," {}\n",[32,530,532],{"class":34,"line":531},50,[32,533,58],{"emptyLinePlaceholder":57},[32,535,537,539,541,543,545,547,549,551],{"class":34,"line":536},51,[32,538,194],{"class":64},[32,540,197],{"class":152},[32,542,200],{"class":68},[32,544,203],{"class":90},[32,546,206],{"class":68},[32,548,209],{"class":64},[32,550,212],{"class":90},[32,552,423],{"class":68},[32,554,556,559,562,565,568],{"class":34,"line":555},52,[32,557,558],{"class":64},"        return",[32,560,561],{"class":90}," self",[32,563,564],{"class":68},"._store.get(",[32,566,567],{"class":90},"id",[32,569,100],{"class":68},[32,571,573],{"class":34,"line":572},53,[32,574,58],{"emptyLinePlaceholder":57},[32,576,578,580,582],{"class":34,"line":577},54,[32,579,194],{"class":64},[32,581,231],{"class":152},[32,583,584],{"class":68},"(self, entity: T) -> T:\n",[32,586,588,590,593,595,598],{"class":34,"line":587},55,[32,589,515],{"class":90},[32,591,592],{"class":68},"._store[entity.id] ",[32,594,170],{"class":64},[32,596,597],{"class":68}," entity   ",[32,599,600],{"class":38},"# type: ignore[attr-defined] — Entity has .id\n",[32,602,604,606],{"class":34,"line":603},56,[32,605,558],{"class":64},[32,607,608],{"class":68}," entity\n",[32,610,612],{"class":34,"line":611},57,[32,613,58],{"emptyLinePlaceholder":57},[32,615,617,619,621,623,625,627,629],{"class":34,"line":616},58,[32,618,194],{"class":64},[32,620,249],{"class":152},[32,622,200],{"class":68},[32,624,203],{"class":90},[32,626,256],{"class":68},[32,628,259],{"class":90},[32,630,423],{"class":68},[32,632,634,636,638,641,643,645,647,650,653,656],{"class":34,"line":633},59,[32,635,558],{"class":64},[32,637,561],{"class":90},[32,639,640],{"class":68},"._store.pop(",[32,642,567],{"class":90},[32,644,163],{"class":68},[32,646,507],{"class":90},[32,648,649],{"class":68},") ",[32,651,652],{"class":64},"is",[32,654,655],{"class":64}," not",[32,657,658],{"class":90}," None\n",[32,660,662],{"class":34,"line":661},60,[32,663,58],{"emptyLinePlaceholder":57},[32,665,667,669,672],{"class":34,"line":666},61,[32,668,194],{"class":64},[32,670,671],{"class":152}," find_all",[32,673,674],{"class":68},"(self) -> list[T]:\n",[32,676,678,680,683,685,688],{"class":34,"line":677},62,[32,679,558],{"class":64},[32,681,682],{"class":90}," list",[32,684,285],{"class":68},[32,686,687],{"class":90},"self",[32,689,690],{"class":68},"._store.values())\n",[32,692,694],{"class":34,"line":693},63,[32,695,58],{"emptyLinePlaceholder":57},[32,697,699],{"class":34,"line":698},64,[32,700,701],{"class":38},"# ── Usage: mypy verifies the entire chain ──\n",[32,703,705,708,710],{"class":34,"line":704},65,[32,706,707],{"class":68},"user_repo: InMemoryRepository[User] ",[32,709,170],{"class":64},[32,711,712],{"class":68}," InMemoryRepository()\n",[32,714,716,719,721,723,726,728,731,733,736,738,741,743,746],{"class":34,"line":715},66,[32,717,718],{"class":68},"user_repo.save(User(",[32,720,567],{"class":166},[32,722,170],{"class":64},[32,724,725],{"class":90},"1",[32,727,163],{"class":68},[32,729,730],{"class":166},"email",[32,732,170],{"class":64},[32,734,735],{"class":159},"\"ada@example.com\"",[32,737,163],{"class":68},[32,739,740],{"class":166},"name",[32,742,170],{"class":64},[32,744,745],{"class":159},"\"Ada\"",[32,747,748],{"class":68},"))\n",[32,750,752],{"class":34,"line":751},67,[32,753,58],{"emptyLinePlaceholder":57},[32,755,757,760,762,765,767,770],{"class":34,"line":756},68,[32,758,759],{"class":68},"found ",[32,761,170],{"class":64},[32,763,764],{"class":68}," user_repo.get_by_id(",[32,766,725],{"class":90},[32,768,769],{"class":68},")   ",[32,771,772],{"class":38},"# mypy infers: User | None\n",[32,774,776,779],{"class":34,"line":775},69,[32,777,778],{"class":64},"if",[32,780,781],{"class":68}," found:\n",[32,783,785,788,791],{"class":34,"line":784},70,[32,786,787],{"class":90},"    print",[32,789,790],{"class":68},"(found.email)            ",[32,792,793],{"class":38},"# mypy knows `found` is User here (narrowed by `if`)\n",[32,795,797],{"class":34,"line":796},71,[32,798,58],{"emptyLinePlaceholder":57},[32,800,802],{"class":34,"line":801},72,[32,803,804],{"class":38},"# mypy catches type mismatches at the call site:\n",[32,806,808],{"class":34,"line":807},73,[32,809,810],{"class":38},"# user_repo.save(\"not a user\")   # mypy error: Argument 1 has incompatible type \"str\"; expected \"User\"\n",[19,812,813],{"language":21},[23,814,816],{"className":25,"code":815,"language":21,"meta":27,"style":27},"# ── TYPE_CHECKING: avoid runtime import costs for type-only imports ──\n# Imports inside `if TYPE_CHECKING:` are invisible at runtime — no circular imports,\n# no heavy module load, but mypy\u002Fpyright see them and type-check correctly.\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    # These imports exist ONLY for the type checker — zero runtime cost\n    import expensive_module   # would be slow\u002Fcircular at runtime, fine for mypy\n    from myapp.models import HeavyModel   # forward reference, resolved by mypy only\n\ndef process(item: \"HeavyModel\") -> \"expensive_module.Result\":\n    # The string annotations (\"HeavyModel\") are not evaluated at runtime —\n    # they're parsed as strings and resolved by mypy from the TYPE_CHECKING imports\n    return item.process()   # type: ignore[attr-defined] — runtime doesn't know the type\n\n# ── Final: preventing reassignment of constants and overriding of methods ──\nMAX_CONNECTIONS: Final[int] = 100   # mypy flags any reassignment as an error\n# MAX_CONNECTIONS = 200   # mypy error: Cannot assign to final name\n\nclass BaseService:\n    def handle(self) -> None: ...\n\nclass CachedService(BaseService):\n    @property\n    def is_cached(self) -> bool: ...\n\n# ── overload: multiple signatures for the same function ──\n@overload\ndef parse_value(raw: str) -> str: ...\n@overload\ndef parse_value(raw: bytes) -> bytes: ...\ndef parse_value(raw: str | bytes) -> str | bytes:\n    \"\"\"Actual implementation — mypy uses the overloads for call-site type checking.\"\"\"\n    return raw.strip()\n\n# mypy knows: parse_value(\"x\") returns str, parse_value(b\"x\") returns bytes\nresult_str: str = parse_value(\"  hello  \")     # mypy: OK — str overload\nresult_bytes: bytes = parse_value(b\"  hello  \")  # mypy: OK — bytes overload\n",[29,817,818,823,828,833,837,848,852,861,866,877,893,897,918,923,928,939,943,948,969,974,978,987,1002,1006,1020,1028,1043,1047,1052,1057,1077,1081,1100,1126,1131,1138,1142,1147,1169],{"__ignoreMap":27},[32,819,820],{"class":34,"line":35},[32,821,822],{"class":38},"# ── TYPE_CHECKING: avoid runtime import costs for type-only imports ──\n",[32,824,825],{"class":34,"line":42},[32,826,827],{"class":38},"# Imports inside `if TYPE_CHECKING:` are invisible at runtime — no circular imports,\n",[32,829,830],{"class":34,"line":48},[32,831,832],{"class":38},"# no heavy module load, but mypy\u002Fpyright see them and type-check correctly.\n",[32,834,835],{"class":34,"line":54},[32,836,58],{"emptyLinePlaceholder":57},[32,838,839,841,843,845],{"class":34,"line":61},[32,840,65],{"class":64},[32,842,69],{"class":68},[32,844,72],{"class":64},[32,846,847],{"class":90}," TYPE_CHECKING\n",[32,849,850],{"class":34,"line":78},[32,851,58],{"emptyLinePlaceholder":57},[32,853,854,856,859],{"class":34,"line":84},[32,855,778],{"class":64},[32,857,858],{"class":90}," TYPE_CHECKING",[32,860,423],{"class":68},[32,862,863],{"class":34,"line":97},[32,864,865],{"class":38},"    # These imports exist ONLY for the type checker — zero runtime cost\n",[32,867,868,871,874],{"class":34,"line":103},[32,869,870],{"class":64},"    import",[32,872,873],{"class":68}," expensive_module   ",[32,875,876],{"class":38},"# would be slow\u002Fcircular at runtime, fine for mypy\n",[32,878,879,882,885,887,890],{"class":34,"line":116},[32,880,881],{"class":64},"    from",[32,883,884],{"class":68}," myapp.models ",[32,886,72],{"class":64},[32,888,889],{"class":68}," HeavyModel   ",[32,891,892],{"class":38},"# forward reference, resolved by mypy only\n",[32,894,895],{"class":34,"line":129},[32,896,58],{"emptyLinePlaceholder":57},[32,898,899,902,905,908,911,913,916],{"class":34,"line":134},[32,900,901],{"class":64},"def",[32,903,904],{"class":152}," process",[32,906,907],{"class":68},"(item: ",[32,909,910],{"class":159},"\"HeavyModel\"",[32,912,256],{"class":68},[32,914,915],{"class":159},"\"expensive_module.Result\"",[32,917,423],{"class":68},[32,919,920],{"class":34,"line":140},[32,921,922],{"class":38},"    # The string annotations (\"HeavyModel\") are not evaluated at runtime —\n",[32,924,925],{"class":34,"line":146},[32,926,927],{"class":38},"    # they're parsed as strings and resolved by mypy from the TYPE_CHECKING imports\n",[32,929,930,933,936],{"class":34,"line":179},[32,931,932],{"class":64},"    return",[32,934,935],{"class":68}," item.process()   ",[32,937,938],{"class":38},"# type: ignore[attr-defined] — runtime doesn't know the type\n",[32,940,941],{"class":34,"line":185},[32,942,58],{"emptyLinePlaceholder":57},[32,944,945],{"class":34,"line":191},[32,946,947],{"class":38},"# ── Final: preventing reassignment of constants and overriding of methods ──\n",[32,949,950,953,956,958,961,963,966],{"class":34,"line":221},[32,951,952],{"class":90},"MAX_CONNECTIONS",[32,954,955],{"class":68},": Final[",[32,957,203],{"class":90},[32,959,960],{"class":68},"] ",[32,962,170],{"class":64},[32,964,965],{"class":90}," 100",[32,967,968],{"class":38},"   # mypy flags any reassignment as an error\n",[32,970,971],{"class":34,"line":226},[32,972,973],{"class":38},"# MAX_CONNECTIONS = 200   # mypy error: Cannot assign to final name\n",[32,975,976],{"class":34,"line":239},[32,977,58],{"emptyLinePlaceholder":57},[32,979,980,982,985],{"class":34,"line":244},[32,981,149],{"class":64},[32,983,984],{"class":152}," BaseService",[32,986,423],{"class":68},[32,988,989,991,994,996,998,1000],{"class":34,"line":266},[32,990,194],{"class":64},[32,992,993],{"class":152}," handle",[32,995,504],{"class":68},[32,997,507],{"class":90},[32,999,215],{"class":68},[32,1001,218],{"class":90},[32,1003,1004],{"class":34,"line":271},[32,1005,58],{"emptyLinePlaceholder":57},[32,1007,1008,1010,1013,1015,1018],{"class":34,"line":277},[32,1009,149],{"class":64},[32,1011,1012],{"class":152}," CachedService",[32,1014,285],{"class":68},[32,1016,1017],{"class":152},"BaseService",[32,1019,291],{"class":68},[32,1021,1022,1025],{"class":34,"line":294},[32,1023,1024],{"class":152},"    @",[32,1026,1027],{"class":90},"property\n",[32,1029,1030,1032,1035,1037,1039,1041],{"class":34,"line":305},[32,1031,194],{"class":64},[32,1033,1034],{"class":152}," is_cached",[32,1036,504],{"class":68},[32,1038,259],{"class":90},[32,1040,215],{"class":68},[32,1042,218],{"class":90},[32,1044,1045],{"class":34,"line":314},[32,1046,58],{"emptyLinePlaceholder":57},[32,1048,1049],{"class":34,"line":322},[32,1050,1051],{"class":38},"# ── overload: multiple signatures for the same function ──\n",[32,1053,1054],{"class":34,"line":334},[32,1055,1056],{"class":152},"@overload\n",[32,1058,1059,1061,1064,1067,1069,1071,1073,1075],{"class":34,"line":352},[32,1060,901],{"class":64},[32,1062,1063],{"class":152}," parse_value",[32,1065,1066],{"class":68},"(raw: ",[32,1068,328],{"class":90},[32,1070,256],{"class":68},[32,1072,328],{"class":90},[32,1074,215],{"class":68},[32,1076,218],{"class":90},[32,1078,1079],{"class":34,"line":357},[32,1080,1056],{"class":152},[32,1082,1083,1085,1087,1089,1092,1094,1096,1098],{"class":34,"line":371},[32,1084,901],{"class":64},[32,1086,1063],{"class":152},[32,1088,1066],{"class":68},[32,1090,1091],{"class":90},"bytes",[32,1093,256],{"class":68},[32,1095,1091],{"class":90},[32,1097,215],{"class":68},[32,1099,218],{"class":90},[32,1101,1102,1104,1106,1108,1110,1113,1116,1118,1120,1122,1124],{"class":34,"line":378},[32,1103,901],{"class":64},[32,1105,1063],{"class":152},[32,1107,1066],{"class":68},[32,1109,328],{"class":90},[32,1111,1112],{"class":64}," |",[32,1114,1115],{"class":90}," bytes",[32,1117,256],{"class":68},[32,1119,328],{"class":90},[32,1121,1112],{"class":64},[32,1123,1115],{"class":90},[32,1125,423],{"class":68},[32,1127,1128],{"class":34,"line":385},[32,1129,1130],{"class":159},"    \"\"\"Actual implementation — mypy uses the overloads for call-site type checking.\"\"\"\n",[32,1132,1133,1135],{"class":34,"line":398},[32,1134,932],{"class":64},[32,1136,1137],{"class":68}," raw.strip()\n",[32,1139,1140],{"class":34,"line":403},[32,1141,58],{"emptyLinePlaceholder":57},[32,1143,1144],{"class":34,"line":409},[32,1145,1146],{"class":38},"# mypy knows: parse_value(\"x\") returns str, parse_value(b\"x\") returns bytes\n",[32,1148,1149,1152,1154,1157,1160,1163,1166],{"class":34,"line":415},[32,1150,1151],{"class":68},"result_str: ",[32,1153,328],{"class":90},[32,1155,1156],{"class":64}," =",[32,1158,1159],{"class":68}," parse_value(",[32,1161,1162],{"class":159},"\"  hello  \"",[32,1164,1165],{"class":68},")     ",[32,1167,1168],{"class":38},"# mypy: OK — str overload\n",[32,1170,1171,1174,1176,1178,1180,1183,1185,1188],{"class":34,"line":426},[32,1172,1173],{"class":68},"result_bytes: ",[32,1175,1091],{"class":90},[32,1177,1156],{"class":64},[32,1179,1159],{"class":68},[32,1181,1182],{"class":64},"b",[32,1184,1162],{"class":159},[32,1186,1187],{"class":68},")  ",[32,1189,1190],{"class":38},"# mypy: OK — bytes overload\n",[14,1192,1194],{"id":1193},"basic-annotations-variables-parameters-return-types","Basic Annotations: Variables, Parameters, Return Types",[19,1196,1197],{"language":21},[23,1198,1200],{"className":25,"code":1199,"language":21,"meta":27,"style":27},"age: int = 30\nname: str = \"Grace\"\nscores: list[int] = [95, 88, 76]          # built-in generics (3.9+) — no need to import List\nlookup: dict[str, int] = {\"a\": 1, \"b\": 2}\n\ndef average(values: list[float]) -> float:\n    return sum(values) \u002F len(values)\n\ndef log(message: str, level: int = 1) -> None:   # -> None means \"no meaningful return value\"\n    print(f\"[{level}] {message}\")\n",[29,1201,1202,1214,1226,1259,1297,1301,1321,1340,1344,1376],{"__ignoreMap":27},[32,1203,1204,1207,1209,1211],{"class":34,"line":35},[32,1205,1206],{"class":68},"age: ",[32,1208,203],{"class":90},[32,1210,1156],{"class":64},[32,1212,1213],{"class":90}," 30\n",[32,1215,1216,1219,1221,1223],{"class":34,"line":42},[32,1217,1218],{"class":68},"name: ",[32,1220,328],{"class":90},[32,1222,1156],{"class":64},[32,1224,1225],{"class":159}," \"Grace\"\n",[32,1227,1228,1231,1233,1235,1237,1240,1243,1245,1248,1250,1253,1256],{"class":34,"line":48},[32,1229,1230],{"class":68},"scores: list[",[32,1232,203],{"class":90},[32,1234,960],{"class":68},[32,1236,170],{"class":64},[32,1238,1239],{"class":68}," [",[32,1241,1242],{"class":90},"95",[32,1244,163],{"class":68},[32,1246,1247],{"class":90},"88",[32,1249,163],{"class":68},[32,1251,1252],{"class":90},"76",[32,1254,1255],{"class":68},"]          ",[32,1257,1258],{"class":38},"# built-in generics (3.9+) — no need to import List\n",[32,1260,1261,1264,1266,1268,1270,1272,1274,1277,1280,1282,1284,1286,1289,1291,1294],{"class":34,"line":54},[32,1262,1263],{"class":68},"lookup: dict[",[32,1265,328],{"class":90},[32,1267,163],{"class":68},[32,1269,203],{"class":90},[32,1271,960],{"class":68},[32,1273,170],{"class":64},[32,1275,1276],{"class":68}," {",[32,1278,1279],{"class":159},"\"a\"",[32,1281,215],{"class":68},[32,1283,725],{"class":90},[32,1285,163],{"class":68},[32,1287,1288],{"class":159},"\"b\"",[32,1290,215],{"class":68},[32,1292,1293],{"class":90},"2",[32,1295,1296],{"class":68},"}\n",[32,1298,1299],{"class":34,"line":61},[32,1300,58],{"emptyLinePlaceholder":57},[32,1302,1303,1305,1308,1311,1314,1317,1319],{"class":34,"line":78},[32,1304,901],{"class":64},[32,1306,1307],{"class":152}," average",[32,1309,1310],{"class":68},"(values: list[",[32,1312,1313],{"class":90},"float",[32,1315,1316],{"class":68},"]) -> ",[32,1318,1313],{"class":90},[32,1320,423],{"class":68},[32,1322,1323,1325,1328,1331,1334,1337],{"class":34,"line":84},[32,1324,932],{"class":64},[32,1326,1327],{"class":90}," sum",[32,1329,1330],{"class":68},"(values) ",[32,1332,1333],{"class":64},"\u002F",[32,1335,1336],{"class":90}," len",[32,1338,1339],{"class":68},"(values)\n",[32,1341,1342],{"class":34,"line":97},[32,1343,58],{"emptyLinePlaceholder":57},[32,1345,1346,1348,1351,1354,1356,1359,1361,1363,1366,1368,1370,1373],{"class":34,"line":103},[32,1347,901],{"class":64},[32,1349,1350],{"class":152}," log",[32,1352,1353],{"class":68},"(message: ",[32,1355,328],{"class":90},[32,1357,1358],{"class":68},", level: ",[32,1360,203],{"class":90},[32,1362,1156],{"class":64},[32,1364,1365],{"class":90}," 1",[32,1367,256],{"class":68},[32,1369,507],{"class":90},[32,1371,1372],{"class":68},":   ",[32,1374,1375],{"class":38},"# -> None means \"no meaningful return value\"\n",[32,1377,1378,1380,1382,1385,1388,1391,1394,1397,1399,1401,1404,1406,1409],{"class":34,"line":116},[32,1379,787],{"class":90},[32,1381,285],{"class":68},[32,1383,1384],{"class":64},"f",[32,1386,1387],{"class":159},"\"[",[32,1389,1390],{"class":90},"{",[32,1392,1393],{"class":68},"level",[32,1395,1396],{"class":90},"}",[32,1398,960],{"class":159},[32,1400,1390],{"class":90},[32,1402,1403],{"class":68},"message",[32,1405,1396],{"class":90},[32,1407,1408],{"class":159},"\"",[32,1410,100],{"class":68},[1412,1413,1414,1415,215,1418,163,1421,163,1424,1427,1428,1333,1431,1434],"p",{},"Before Python 3.9, generics required importing from ",[29,1416,1417],{},"typing",[29,1419,1420],{},"List[int]",[29,1422,1423],{},"Dict[str, int]",[29,1425,1426],{},"Tuple[int, ...]",". These still work (and are required pre-3.9), but ",[29,1429,1430],{},"list[int]",[29,1432,1433],{},"dict[str, int]"," using the built-ins directly is the modern, preferred form.",[19,1436,1437],{"language":21},[23,1438,1440],{"className":25,"code":1439,"language":21,"meta":27,"style":27},"from typing import List, Dict, Tuple   # legacy pre-3.9 style — still valid, but prefer built-ins now\n\nlegacy_scores: List[int] = [1, 2, 3]\nmodern_scores: list[int] = [1, 2, 3]     # identical meaning, no import needed\n",[29,1441,1442,1456,1460,1486],{"__ignoreMap":27},[32,1443,1444,1446,1448,1450,1453],{"class":34,"line":35},[32,1445,65],{"class":64},[32,1447,69],{"class":68},[32,1449,72],{"class":64},[32,1451,1452],{"class":68}," List, Dict, Tuple   ",[32,1454,1455],{"class":38},"# legacy pre-3.9 style — still valid, but prefer built-ins now\n",[32,1457,1458],{"class":34,"line":42},[32,1459,58],{"emptyLinePlaceholder":57},[32,1461,1462,1465,1467,1469,1471,1473,1475,1477,1479,1481,1484],{"class":34,"line":48},[32,1463,1464],{"class":68},"legacy_scores: List[",[32,1466,203],{"class":90},[32,1468,960],{"class":68},[32,1470,170],{"class":64},[32,1472,1239],{"class":68},[32,1474,725],{"class":90},[32,1476,163],{"class":68},[32,1478,1293],{"class":90},[32,1480,163],{"class":68},[32,1482,1483],{"class":90},"3",[32,1485,331],{"class":68},[32,1487,1488,1491,1493,1495,1497,1499,1501,1503,1505,1507,1509,1512],{"class":34,"line":54},[32,1489,1490],{"class":68},"modern_scores: list[",[32,1492,203],{"class":90},[32,1494,960],{"class":68},[32,1496,170],{"class":64},[32,1498,1239],{"class":68},[32,1500,725],{"class":90},[32,1502,163],{"class":68},[32,1504,1293],{"class":90},[32,1506,163],{"class":68},[32,1508,1483],{"class":90},[32,1510,1511],{"class":68},"]     ",[32,1513,1514],{"class":38},"# identical meaning, no import needed\n",[14,1516,1518,1521,1522,1525],{"id":1517},"optional-and-union-values-that-can-be-more-than-one-type",[29,1519,1520],{},"Optional"," and ",[29,1523,1524],{},"Union",": Values That Can Be More Than One Type",[19,1527,1528],{"language":21},[23,1529,1531],{"className":25,"code":1530,"language":21,"meta":27,"style":27},"from typing import Optional, Union\n\ndef find_user(user_id: int) -> Optional[str]:      # Optional[str] means str | None\n    return database.get(user_id)                      # returns None if not found\n\ndef parse_id(raw: Union[str, int]) -> int:           # accepts EITHER a str or an int\n    return int(raw)\n\n# Python 3.10+ pipe syntax — preferred, no import needed\ndef find_user_modern(user_id: int) -> str | None:\n    return database.get(user_id)\n\ndef parse_id_modern(raw: str | int) -> int:\n    return int(raw)\n",[29,1532,1533,1544,1548,1571,1581,1585,1611,1621,1625,1630,1651,1658,1662,1683],{"__ignoreMap":27},[32,1534,1535,1537,1539,1541],{"class":34,"line":35},[32,1536,65],{"class":64},[32,1538,69],{"class":68},[32,1540,72],{"class":64},[32,1542,1543],{"class":68}," Optional, Union\n",[32,1545,1546],{"class":34,"line":42},[32,1547,58],{"emptyLinePlaceholder":57},[32,1549,1550,1552,1555,1558,1560,1563,1565,1568],{"class":34,"line":48},[32,1551,901],{"class":64},[32,1553,1554],{"class":152}," find_user",[32,1556,1557],{"class":68},"(user_id: ",[32,1559,203],{"class":90},[32,1561,1562],{"class":68},") -> Optional[",[32,1564,328],{"class":90},[32,1566,1567],{"class":68},"]:      ",[32,1569,1570],{"class":38},"# Optional[str] means str | None\n",[32,1572,1573,1575,1578],{"class":34,"line":54},[32,1574,932],{"class":64},[32,1576,1577],{"class":68}," database.get(user_id)                      ",[32,1579,1580],{"class":38},"# returns None if not found\n",[32,1582,1583],{"class":34,"line":61},[32,1584,58],{"emptyLinePlaceholder":57},[32,1586,1587,1589,1592,1595,1597,1599,1601,1603,1605,1608],{"class":34,"line":78},[32,1588,901],{"class":64},[32,1590,1591],{"class":152}," parse_id",[32,1593,1594],{"class":68},"(raw: Union[",[32,1596,328],{"class":90},[32,1598,163],{"class":68},[32,1600,203],{"class":90},[32,1602,1316],{"class":68},[32,1604,203],{"class":90},[32,1606,1607],{"class":68},":           ",[32,1609,1610],{"class":38},"# accepts EITHER a str or an int\n",[32,1612,1613,1615,1618],{"class":34,"line":84},[32,1614,932],{"class":64},[32,1616,1617],{"class":90}," int",[32,1619,1620],{"class":68},"(raw)\n",[32,1622,1623],{"class":34,"line":97},[32,1624,58],{"emptyLinePlaceholder":57},[32,1626,1627],{"class":34,"line":103},[32,1628,1629],{"class":38},"# Python 3.10+ pipe syntax — preferred, no import needed\n",[32,1631,1632,1634,1637,1639,1641,1643,1645,1647,1649],{"class":34,"line":116},[32,1633,901],{"class":64},[32,1635,1636],{"class":152}," find_user_modern",[32,1638,1557],{"class":68},[32,1640,203],{"class":90},[32,1642,256],{"class":68},[32,1644,328],{"class":90},[32,1646,1112],{"class":64},[32,1648,212],{"class":90},[32,1650,423],{"class":68},[32,1652,1653,1655],{"class":34,"line":129},[32,1654,932],{"class":64},[32,1656,1657],{"class":68}," database.get(user_id)\n",[32,1659,1660],{"class":34,"line":134},[32,1661,58],{"emptyLinePlaceholder":57},[32,1663,1664,1666,1669,1671,1673,1675,1677,1679,1681],{"class":34,"line":140},[32,1665,901],{"class":64},[32,1667,1668],{"class":152}," parse_id_modern",[32,1670,1066],{"class":68},[32,1672,328],{"class":90},[32,1674,1112],{"class":64},[32,1676,1617],{"class":90},[32,1678,256],{"class":68},[32,1680,203],{"class":90},[32,1682,423],{"class":68},[32,1684,1685,1687,1689],{"class":34,"line":146},[32,1686,932],{"class":64},[32,1688,1617],{"class":90},[32,1690,1620],{"class":68},[1412,1692,1693,1696,1697,1700,1701,215,1705,1708,1709,1711,1712,1715],{},[29,1694,1695],{},"Optional[X]"," is exactly ",[29,1698,1699],{},"Union[X, None]"," — it is not a special \"maybe present\" wrapper type at runtime, just shorthand. ",[1702,1703,1704],"strong",{},"The gotcha",[29,1706,1707],{},"Optional[str]"," does NOT mean the parameter has a default value of ",[29,1710,507],{}," — the two are independent, and forgetting the explicit ",[29,1713,1714],{},"= None"," default is a common mistake:",[19,1717,1718],{"language":21},[23,1719,1721],{"className":25,"code":1720,"language":21,"meta":27,"style":27},"# WRONG mental model — Optional[str] does NOT supply a default\ndef greet(name: Optional[str]) -> str:\n    return f\"Hello, {name or 'stranger'}\"\n\ngreet()   # TypeError: missing 1 required positional argument — Optional didn't make it optional to PASS!\n\n# CORRECT — the default is a separate, explicit declaration\ndef greet(name: Optional[str] = None) -> str:\n    return f\"Hello, {name or 'stranger'}\"\n\ngreet()   # \"Hello, stranger\" — works, because of `= None`, not because of Optional\n",[29,1722,1723,1728,1746,1772,1776,1784,1788,1793,1815,1835,1839],{"__ignoreMap":27},[32,1724,1725],{"class":34,"line":35},[32,1726,1727],{"class":38},"# WRONG mental model — Optional[str] does NOT supply a default\n",[32,1729,1730,1732,1735,1738,1740,1742,1744],{"class":34,"line":42},[32,1731,901],{"class":64},[32,1733,1734],{"class":152}," greet",[32,1736,1737],{"class":68},"(name: Optional[",[32,1739,328],{"class":90},[32,1741,1316],{"class":68},[32,1743,328],{"class":90},[32,1745,423],{"class":68},[32,1747,1748,1750,1753,1756,1758,1761,1764,1767,1769],{"class":34,"line":48},[32,1749,932],{"class":64},[32,1751,1752],{"class":64}," f",[32,1754,1755],{"class":159},"\"Hello, ",[32,1757,1390],{"class":90},[32,1759,1760],{"class":68},"name ",[32,1762,1763],{"class":64},"or",[32,1765,1766],{"class":159}," 'stranger'",[32,1768,1396],{"class":90},[32,1770,1771],{"class":159},"\"\n",[32,1773,1774],{"class":34,"line":54},[32,1775,58],{"emptyLinePlaceholder":57},[32,1777,1778,1781],{"class":34,"line":61},[32,1779,1780],{"class":68},"greet()   ",[32,1782,1783],{"class":38},"# TypeError: missing 1 required positional argument — Optional didn't make it optional to PASS!\n",[32,1785,1786],{"class":34,"line":78},[32,1787,58],{"emptyLinePlaceholder":57},[32,1789,1790],{"class":34,"line":84},[32,1791,1792],{"class":38},"# CORRECT — the default is a separate, explicit declaration\n",[32,1794,1795,1797,1799,1801,1803,1805,1807,1809,1811,1813],{"class":34,"line":97},[32,1796,901],{"class":64},[32,1798,1734],{"class":152},[32,1800,1737],{"class":68},[32,1802,328],{"class":90},[32,1804,960],{"class":68},[32,1806,170],{"class":64},[32,1808,212],{"class":90},[32,1810,256],{"class":68},[32,1812,328],{"class":90},[32,1814,423],{"class":68},[32,1816,1817,1819,1821,1823,1825,1827,1829,1831,1833],{"class":34,"line":103},[32,1818,932],{"class":64},[32,1820,1752],{"class":64},[32,1822,1755],{"class":159},[32,1824,1390],{"class":90},[32,1826,1760],{"class":68},[32,1828,1763],{"class":64},[32,1830,1766],{"class":159},[32,1832,1396],{"class":90},[32,1834,1771],{"class":159},[32,1836,1837],{"class":34,"line":116},[32,1838,58],{"emptyLinePlaceholder":57},[32,1840,1841,1843],{"class":34,"line":129},[32,1842,1780],{"class":68},[32,1844,1845],{"class":38},"# \"Hello, stranger\" — works, because of `= None`, not because of Optional\n",[14,1847,1849,1850,1853],{"id":1848},"generics-typevar-and-generic-classesfunctions","Generics: ",[29,1851,1852],{},"TypeVar"," and Generic Classes\u002FFunctions",[19,1855,1856],{"language":21},[23,1857,1859],{"className":25,"code":1858,"language":21,"meta":27,"style":27},"from typing import TypeVar, Generic\n\nT = TypeVar(\"T\")\n\ndef first(items: list[T]) -> T:            # T is a placeholder — whatever type is passed in, comes back out\n    return items[0]\n\nreveal_result_int = first([1, 2, 3])         # mypy infers: int\nreveal_result_str = first([\"a\", \"b\"])          # mypy infers: str\n\nclass Stack(Generic[T]):\n    def __init__(self) -> None:\n        self._items: list[T] = []\n\n    def push(self, item: T) -> None:\n        self._items.append(item)\n\n    def pop(self) -> T:\n        return self._items.pop()\n\nint_stack: Stack[int] = Stack()\nint_stack.push(5)\n# int_stack.push(\"oops\")   # mypy error: Argument has incompatible type \"str\"; expected \"int\"\n",[29,1860,1861,1872,1876,1890,1894,1907,1919,1923,1949,1970,1974,1983,1995,2007,2011,2025,2032,2036,2046,2055,2059,2073,2083],{"__ignoreMap":27},[32,1862,1863,1865,1867,1869],{"class":34,"line":35},[32,1864,65],{"class":64},[32,1866,69],{"class":68},[32,1868,72],{"class":64},[32,1870,1871],{"class":68}," TypeVar, Generic\n",[32,1873,1874],{"class":34,"line":42},[32,1875,58],{"emptyLinePlaceholder":57},[32,1877,1878,1881,1883,1886,1888],{"class":34,"line":48},[32,1879,1880],{"class":68},"T ",[32,1882,170],{"class":64},[32,1884,1885],{"class":68}," TypeVar(",[32,1887,160],{"class":159},[32,1889,100],{"class":68},[32,1891,1892],{"class":34,"line":54},[32,1893,58],{"emptyLinePlaceholder":57},[32,1895,1896,1898,1901,1904],{"class":34,"line":61},[32,1897,901],{"class":64},[32,1899,1900],{"class":152}," first",[32,1902,1903],{"class":68},"(items: list[T]) -> T:            ",[32,1905,1906],{"class":38},"# T is a placeholder — whatever type is passed in, comes back out\n",[32,1908,1909,1911,1914,1917],{"class":34,"line":78},[32,1910,932],{"class":64},[32,1912,1913],{"class":68}," items[",[32,1915,1916],{"class":90},"0",[32,1918,331],{"class":68},[32,1920,1921],{"class":34,"line":84},[32,1922,58],{"emptyLinePlaceholder":57},[32,1924,1925,1928,1930,1933,1935,1937,1939,1941,1943,1946],{"class":34,"line":97},[32,1926,1927],{"class":68},"reveal_result_int ",[32,1929,170],{"class":64},[32,1931,1932],{"class":68}," first([",[32,1934,725],{"class":90},[32,1936,163],{"class":68},[32,1938,1293],{"class":90},[32,1940,163],{"class":68},[32,1942,1483],{"class":90},[32,1944,1945],{"class":68},"])         ",[32,1947,1948],{"class":38},"# mypy infers: int\n",[32,1950,1951,1954,1956,1958,1960,1962,1964,1967],{"class":34,"line":103},[32,1952,1953],{"class":68},"reveal_result_str ",[32,1955,170],{"class":64},[32,1957,1932],{"class":68},[32,1959,1279],{"class":159},[32,1961,163],{"class":68},[32,1963,1288],{"class":159},[32,1965,1966],{"class":68},"])          ",[32,1968,1969],{"class":38},"# mypy infers: str\n",[32,1971,1972],{"class":34,"line":116},[32,1973,58],{"emptyLinePlaceholder":57},[32,1975,1976,1978,1981],{"class":34,"line":129},[32,1977,149],{"class":64},[32,1979,1980],{"class":152}," Stack",[32,1982,487],{"class":68},[32,1984,1985,1987,1989,1991,1993],{"class":34,"line":134},[32,1986,194],{"class":64},[32,1988,501],{"class":90},[32,1990,504],{"class":68},[32,1992,507],{"class":90},[32,1994,423],{"class":68},[32,1996,1997,1999,2002,2004],{"class":34,"line":140},[32,1998,515],{"class":90},[32,2000,2001],{"class":68},"._items: list[T] ",[32,2003,170],{"class":64},[32,2005,2006],{"class":68}," []\n",[32,2008,2009],{"class":34,"line":146},[32,2010,58],{"emptyLinePlaceholder":57},[32,2012,2013,2015,2018,2021,2023],{"class":34,"line":179},[32,2014,194],{"class":64},[32,2016,2017],{"class":152}," push",[32,2019,2020],{"class":68},"(self, item: T) -> ",[32,2022,507],{"class":90},[32,2024,423],{"class":68},[32,2026,2027,2029],{"class":34,"line":185},[32,2028,515],{"class":90},[32,2030,2031],{"class":68},"._items.append(item)\n",[32,2033,2034],{"class":34,"line":191},[32,2035,58],{"emptyLinePlaceholder":57},[32,2037,2038,2040,2043],{"class":34,"line":221},[32,2039,194],{"class":64},[32,2041,2042],{"class":152}," pop",[32,2044,2045],{"class":68},"(self) -> T:\n",[32,2047,2048,2050,2052],{"class":34,"line":226},[32,2049,558],{"class":64},[32,2051,561],{"class":90},[32,2053,2054],{"class":68},"._items.pop()\n",[32,2056,2057],{"class":34,"line":239},[32,2058,58],{"emptyLinePlaceholder":57},[32,2060,2061,2064,2066,2068,2070],{"class":34,"line":244},[32,2062,2063],{"class":68},"int_stack: Stack[",[32,2065,203],{"class":90},[32,2067,960],{"class":68},[32,2069,170],{"class":64},[32,2071,2072],{"class":68}," Stack()\n",[32,2074,2075,2078,2081],{"class":34,"line":266},[32,2076,2077],{"class":68},"int_stack.push(",[32,2079,2080],{"class":90},"5",[32,2082,100],{"class":68},[32,2084,2085],{"class":34,"line":271},[32,2086,2087],{"class":38},"# int_stack.push(\"oops\")   # mypy error: Argument has incompatible type \"str\"; expected \"int\"\n",[1412,2089,2090,2091,2093],{},"Python 3.12 introduced a terser native syntax for generics that replaces ",[29,2092,1852],{}," boilerplate entirely:",[19,2095,2096],{"language":21},[23,2097,2099],{"className":25,"code":2098,"language":21,"meta":27,"style":27},"# Python 3.12+ — no TypeVar import needed\ndef first(items: list[T]) -> T:\n    return items[0]\n\nclass Stack[T]:\n    def __init__(self) -> None:\n        self._items: list[T] = []\n\n    def push(self, item: T) -> None:\n        self._items.append(item)\n\n    def pop(self) -> T:\n        return self._items.pop()\n",[29,2100,2101,2106,2115,2125,2129,2136,2148,2158,2162,2174,2180,2184,2192],{"__ignoreMap":27},[32,2102,2103],{"class":34,"line":35},[32,2104,2105],{"class":38},"# Python 3.12+ — no TypeVar import needed\n",[32,2107,2108,2110,2112],{"class":34,"line":42},[32,2109,901],{"class":64},[32,2111,1900],{"class":152},[32,2113,2114],{"class":68},"(items: list[T]) -> T:\n",[32,2116,2117,2119,2121,2123],{"class":34,"line":48},[32,2118,932],{"class":64},[32,2120,1913],{"class":68},[32,2122,1916],{"class":90},[32,2124,331],{"class":68},[32,2126,2127],{"class":34,"line":54},[32,2128,58],{"emptyLinePlaceholder":57},[32,2130,2131,2133],{"class":34,"line":61},[32,2132,149],{"class":64},[32,2134,2135],{"class":68}," Stack[T]:\n",[32,2137,2138,2140,2142,2144,2146],{"class":34,"line":78},[32,2139,194],{"class":64},[32,2141,501],{"class":90},[32,2143,504],{"class":68},[32,2145,507],{"class":90},[32,2147,423],{"class":68},[32,2149,2150,2152,2154,2156],{"class":34,"line":84},[32,2151,515],{"class":90},[32,2153,2001],{"class":68},[32,2155,170],{"class":64},[32,2157,2006],{"class":68},[32,2159,2160],{"class":34,"line":97},[32,2161,58],{"emptyLinePlaceholder":57},[32,2163,2164,2166,2168,2170,2172],{"class":34,"line":103},[32,2165,194],{"class":64},[32,2167,2017],{"class":152},[32,2169,2020],{"class":68},[32,2171,507],{"class":90},[32,2173,423],{"class":68},[32,2175,2176,2178],{"class":34,"line":116},[32,2177,515],{"class":90},[32,2179,2031],{"class":68},[32,2181,2182],{"class":34,"line":129},[32,2183,58],{"emptyLinePlaceholder":57},[32,2185,2186,2188,2190],{"class":34,"line":134},[32,2187,194],{"class":64},[32,2189,2042],{"class":152},[32,2191,2045],{"class":68},[32,2193,2194,2196,2198],{"class":34,"line":140},[32,2195,558],{"class":64},[32,2197,561],{"class":90},[32,2199,2054],{"class":68},[2201,2202,2204],"h3",{"id":2203},"bounded-and-constrained-typevars","Bounded and constrained TypeVars",[19,2206,2207],{"language":21},[23,2208,2210],{"className":25,"code":2209,"language":21,"meta":27,"style":27},"from typing import TypeVar\n\nNumeric = TypeVar(\"Numeric\", bound=float)     # T must be float OR a subtype (int counts, via numeric tower quirks)\n\ndef double(value: Numeric) -> Numeric:\n    return value * 2\n\nStrOrBytes = TypeVar(\"StrOrBytes\", str, bytes)   # T must be EXACTLY str or EXACTLY bytes — no subclasses substitute freely\n\ndef concat(a: StrOrBytes, b: StrOrBytes) -> StrOrBytes:\n    return a + b\n",[29,2211,2212,2223,2227,2252,2256,2266,2279,2283,2308,2312,2322],{"__ignoreMap":27},[32,2213,2214,2216,2218,2220],{"class":34,"line":35},[32,2215,65],{"class":64},[32,2217,69],{"class":68},[32,2219,72],{"class":64},[32,2221,2222],{"class":68}," TypeVar\n",[32,2224,2225],{"class":34,"line":42},[32,2226,58],{"emptyLinePlaceholder":57},[32,2228,2229,2232,2234,2236,2239,2241,2243,2245,2247,2249],{"class":34,"line":48},[32,2230,2231],{"class":68},"Numeric ",[32,2233,170],{"class":64},[32,2235,1885],{"class":68},[32,2237,2238],{"class":159},"\"Numeric\"",[32,2240,163],{"class":68},[32,2242,167],{"class":166},[32,2244,170],{"class":64},[32,2246,1313],{"class":90},[32,2248,1165],{"class":68},[32,2250,2251],{"class":38},"# T must be float OR a subtype (int counts, via numeric tower quirks)\n",[32,2253,2254],{"class":34,"line":54},[32,2255,58],{"emptyLinePlaceholder":57},[32,2257,2258,2260,2263],{"class":34,"line":61},[32,2259,901],{"class":64},[32,2261,2262],{"class":152}," double",[32,2264,2265],{"class":68},"(value: Numeric) -> Numeric:\n",[32,2267,2268,2270,2273,2276],{"class":34,"line":78},[32,2269,932],{"class":64},[32,2271,2272],{"class":68}," value ",[32,2274,2275],{"class":64},"*",[32,2277,2278],{"class":90}," 2\n",[32,2280,2281],{"class":34,"line":84},[32,2282,58],{"emptyLinePlaceholder":57},[32,2284,2285,2288,2290,2292,2295,2297,2299,2301,2303,2305],{"class":34,"line":97},[32,2286,2287],{"class":68},"StrOrBytes ",[32,2289,170],{"class":64},[32,2291,1885],{"class":68},[32,2293,2294],{"class":159},"\"StrOrBytes\"",[32,2296,163],{"class":68},[32,2298,328],{"class":90},[32,2300,163],{"class":68},[32,2302,1091],{"class":90},[32,2304,769],{"class":68},[32,2306,2307],{"class":38},"# T must be EXACTLY str or EXACTLY bytes — no subclasses substitute freely\n",[32,2309,2310],{"class":34,"line":103},[32,2311,58],{"emptyLinePlaceholder":57},[32,2313,2314,2316,2319],{"class":34,"line":116},[32,2315,901],{"class":64},[32,2317,2318],{"class":152}," concat",[32,2320,2321],{"class":68},"(a: StrOrBytes, b: StrOrBytes) -> StrOrBytes:\n",[32,2323,2324,2326,2329,2332],{"class":34,"line":129},[32,2325,932],{"class":64},[32,2327,2328],{"class":68}," a ",[32,2330,2331],{"class":64},"+",[32,2333,2334],{"class":68}," b\n",[1412,2336,2337,2340,2341,2344,2345,2348],{},[29,2338,2339],{},"bound="," accepts the named type or any subtype (like an upper bound in other languages' generics); the constrained form (listing explicit types as positional args) restricts to exactly those types, each checked independently — mixing ",[29,2342,2343],{},"concat(\"a\", b\"b\")"," is still flagged as an error by ",[29,2346,2347],{},"mypy"," despite both being \"allowed\" types individually.",[14,2350,2352,2355],{"id":2351},"protocol-structural-typing-duck-typing-statically-checked",[29,2353,2354],{},"Protocol",": Structural Typing (Duck Typing, Statically Checked)",[19,2357,2358],{"language":21},[23,2359,2361],{"className":25,"code":2360,"language":21,"meta":27,"style":27},"from typing import Protocol\n\nclass SupportsQuack(Protocol):\n    def quack(self) -> str: ...\n\nclass Duck:\n    def quack(self) -> str:\n        return \"Quack!\"\n\nclass Person:\n    def quack(self) -> str:\n        return \"I'm quacking, I guess?\"\n\ndef make_it_quack(entity: SupportsQuack) -> str:\n    return entity.quack()\n\nprint(make_it_quack(Duck()))     # \"Quack!\"\nprint(make_it_quack(Person()))     # \"I'm quacking, I guess?\" — Person is accepted WITHOUT inheriting SupportsQuack!\n",[29,2362,2363,2374,2378,2391,2406,2410,2419,2431,2438,2442,2451,2463,2470,2474,2488,2495,2499,2510],{"__ignoreMap":27},[32,2364,2365,2367,2369,2371],{"class":34,"line":35},[32,2366,65],{"class":64},[32,2368,69],{"class":68},[32,2370,72],{"class":64},[32,2372,2373],{"class":68}," Protocol\n",[32,2375,2376],{"class":34,"line":42},[32,2377,58],{"emptyLinePlaceholder":57},[32,2379,2380,2382,2385,2387,2389],{"class":34,"line":48},[32,2381,149],{"class":64},[32,2383,2384],{"class":152}," SupportsQuack",[32,2386,285],{"class":68},[32,2388,2354],{"class":152},[32,2390,291],{"class":68},[32,2392,2393,2395,2398,2400,2402,2404],{"class":34,"line":54},[32,2394,194],{"class":64},[32,2396,2397],{"class":152}," quack",[32,2399,504],{"class":68},[32,2401,328],{"class":90},[32,2403,215],{"class":68},[32,2405,218],{"class":90},[32,2407,2408],{"class":34,"line":61},[32,2409,58],{"emptyLinePlaceholder":57},[32,2411,2412,2414,2417],{"class":34,"line":78},[32,2413,149],{"class":64},[32,2415,2416],{"class":152}," Duck",[32,2418,423],{"class":68},[32,2420,2421,2423,2425,2427,2429],{"class":34,"line":84},[32,2422,194],{"class":64},[32,2424,2397],{"class":152},[32,2426,504],{"class":68},[32,2428,328],{"class":90},[32,2430,423],{"class":68},[32,2432,2433,2435],{"class":34,"line":97},[32,2434,558],{"class":64},[32,2436,2437],{"class":159}," \"Quack!\"\n",[32,2439,2440],{"class":34,"line":103},[32,2441,58],{"emptyLinePlaceholder":57},[32,2443,2444,2446,2449],{"class":34,"line":116},[32,2445,149],{"class":64},[32,2447,2448],{"class":152}," Person",[32,2450,423],{"class":68},[32,2452,2453,2455,2457,2459,2461],{"class":34,"line":129},[32,2454,194],{"class":64},[32,2456,2397],{"class":152},[32,2458,504],{"class":68},[32,2460,328],{"class":90},[32,2462,423],{"class":68},[32,2464,2465,2467],{"class":34,"line":134},[32,2466,558],{"class":64},[32,2468,2469],{"class":159}," \"I'm quacking, I guess?\"\n",[32,2471,2472],{"class":34,"line":140},[32,2473,58],{"emptyLinePlaceholder":57},[32,2475,2476,2478,2481,2484,2486],{"class":34,"line":146},[32,2477,901],{"class":64},[32,2479,2480],{"class":152}," make_it_quack",[32,2482,2483],{"class":68},"(entity: SupportsQuack) -> ",[32,2485,328],{"class":90},[32,2487,423],{"class":68},[32,2489,2490,2492],{"class":34,"line":179},[32,2491,932],{"class":64},[32,2493,2494],{"class":68}," entity.quack()\n",[32,2496,2497],{"class":34,"line":185},[32,2498,58],{"emptyLinePlaceholder":57},[32,2500,2501,2504,2507],{"class":34,"line":191},[32,2502,2503],{"class":90},"print",[32,2505,2506],{"class":68},"(make_it_quack(Duck()))     ",[32,2508,2509],{"class":38},"# \"Quack!\"\n",[32,2511,2512,2514,2517],{"class":34,"line":221},[32,2513,2503],{"class":90},[32,2515,2516],{"class":68},"(make_it_quack(Person()))     ",[32,2518,2519],{"class":38},"# \"I'm quacking, I guess?\" — Person is accepted WITHOUT inheriting SupportsQuack!\n",[1412,2521,2522,2523,2526,2527,2529,2530,2533,2534,2537,2538,2541,2542,2545,2546,2550],{},"This is the crucial difference from ",[29,2524,2525],{},"abc.ABC"," (chapter 13): ",[29,2528,2354],{}," implements ",[1702,2531,2532],{},"structural"," typing — any object with a matching ",[29,2535,2536],{},"quack(self) -> str"," method satisfies ",[29,2539,2540],{},"SupportsQuack",", with zero inheritance relationship required, exactly matching Python's runtime duck-typing philosophy but now checkable statically. An ",[29,2543,2544],{},"ABC"," subclass, by contrast, requires ",[2547,2548,2549],"em",{},"explicit"," inheritance to be recognized as satisfying the interface, even if the methods match structurally.",[19,2552,2553],{"language":21},[23,2554,2556],{"className":25,"code":2555,"language":21,"meta":27,"style":27},"from typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass SupportsQuack(Protocol):\n    def quack(self) -> str: ...\n\nprint(isinstance(Person(), SupportsQuack))   # True — runtime_checkable enables isinstance() checks\n# NOTE: runtime_checkable only verifies METHOD NAMES exist, not their signatures or return types!\n",[29,2557,2558,2569,2573,2578,2590,2604,2608,2623],{"__ignoreMap":27},[32,2559,2560,2562,2564,2566],{"class":34,"line":35},[32,2561,65],{"class":64},[32,2563,69],{"class":68},[32,2565,72],{"class":64},[32,2567,2568],{"class":68}," Protocol, runtime_checkable\n",[32,2570,2571],{"class":34,"line":42},[32,2572,58],{"emptyLinePlaceholder":57},[32,2574,2575],{"class":34,"line":48},[32,2576,2577],{"class":152},"@runtime_checkable\n",[32,2579,2580,2582,2584,2586,2588],{"class":34,"line":54},[32,2581,149],{"class":64},[32,2583,2384],{"class":152},[32,2585,285],{"class":68},[32,2587,2354],{"class":152},[32,2589,291],{"class":68},[32,2591,2592,2594,2596,2598,2600,2602],{"class":34,"line":61},[32,2593,194],{"class":64},[32,2595,2397],{"class":152},[32,2597,504],{"class":68},[32,2599,328],{"class":90},[32,2601,215],{"class":68},[32,2603,218],{"class":90},[32,2605,2606],{"class":34,"line":78},[32,2607,58],{"emptyLinePlaceholder":57},[32,2609,2610,2612,2614,2617,2620],{"class":34,"line":84},[32,2611,2503],{"class":90},[32,2613,285],{"class":68},[32,2615,2616],{"class":90},"isinstance",[32,2618,2619],{"class":68},"(Person(), SupportsQuack))   ",[32,2621,2622],{"class":38},"# True — runtime_checkable enables isinstance() checks\n",[32,2624,2625,2628,2631],{"class":34,"line":97},[32,2626,2627],{"class":38},"# ",[32,2629,2630],{"class":64},"NOTE",[32,2632,2633],{"class":38},": runtime_checkable only verifies METHOD NAMES exist, not their signatures or return types!\n",[1412,2635,2636,2639,2640,2642,2643,2646,2647,2649,2650,2652],{},[29,2637,2638],{},"@runtime_checkable"," only checks that the named methods\u002Fattributes exist on the object — it does not verify parameter types or return types match at runtime (that's still ",[29,2641,2347],{},"'s job, statically). An object with a ",[29,2644,2645],{},"quack(self, volume: int) -> None"," method still passes ",[29,2648,2616],{}," against ",[29,2651,2540],{}," even though the signature is completely different.",[14,2654,2656,2658],{"id":2655},"typeddict-typed-dictionary-shapes",[29,2657,288],{},": Typed Dictionary Shapes",[19,2660,2661],{"language":21},[23,2662,2664],{"className":25,"code":2663,"language":21,"meta":27,"style":27},"from typing import TypedDict, NotRequired\n\nclass UserRecord(TypedDict):\n    id: int\n    name: str\n    email: str\n\ndef create_user(data: UserRecord) -> None:\n    print(f\"Creating {data['name']} \u003C{data['email']}>\")\n\ncreate_user({\"id\": 1, \"name\": \"Ada\", \"email\": \"ada@example.com\"})   # OK\n# create_user({\"id\": 1, \"name\": \"Ada\"})   # mypy error: Missing key \"email\"\n\nclass UserRecordPartial(TypedDict):\n    id: int\n    name: str\n    nickname: NotRequired[str]     # 3.11+ — this key may be omitted entirely\n\ncreate_user_partial: UserRecordPartial = {\"id\": 1, \"name\": \"Ada\"}   # OK — nickname is optional\n",[29,2665,2666,2677,2681,2694,2702,2708,2714,2718,2732,2775,2779,2815,2820,2824,2837,2845,2851,2863,2867],{"__ignoreMap":27},[32,2667,2668,2670,2672,2674],{"class":34,"line":35},[32,2669,65],{"class":64},[32,2671,69],{"class":68},[32,2673,72],{"class":64},[32,2675,2676],{"class":68}," TypedDict, NotRequired\n",[32,2678,2679],{"class":34,"line":42},[32,2680,58],{"emptyLinePlaceholder":57},[32,2682,2683,2685,2688,2690,2692],{"class":34,"line":48},[32,2684,149],{"class":64},[32,2686,2687],{"class":152}," UserRecord",[32,2689,285],{"class":68},[32,2691,288],{"class":152},[32,2693,291],{"class":68},[32,2695,2696,2698,2700],{"class":34,"line":54},[32,2697,297],{"class":90},[32,2699,215],{"class":68},[32,2701,302],{"class":90},[32,2703,2704,2706],{"class":34,"line":61},[32,2705,317],{"class":68},[32,2707,311],{"class":90},[32,2709,2710,2712],{"class":34,"line":78},[32,2711,308],{"class":68},[32,2713,311],{"class":90},[32,2715,2716],{"class":34,"line":84},[32,2717,58],{"emptyLinePlaceholder":57},[32,2719,2720,2722,2725,2728,2730],{"class":34,"line":97},[32,2721,901],{"class":64},[32,2723,2724],{"class":152}," create_user",[32,2726,2727],{"class":68},"(data: UserRecord) -> ",[32,2729,507],{"class":90},[32,2731,423],{"class":68},[32,2733,2734,2736,2738,2740,2743,2745,2748,2751,2754,2756,2759,2761,2763,2766,2768,2770,2773],{"class":34,"line":103},[32,2735,787],{"class":90},[32,2737,285],{"class":68},[32,2739,1384],{"class":64},[32,2741,2742],{"class":159},"\"Creating ",[32,2744,1390],{"class":90},[32,2746,2747],{"class":68},"data[",[32,2749,2750],{"class":159},"'name'",[32,2752,2753],{"class":68},"]",[32,2755,1396],{"class":90},[32,2757,2758],{"class":159}," \u003C",[32,2760,1390],{"class":90},[32,2762,2747],{"class":68},[32,2764,2765],{"class":159},"'email'",[32,2767,2753],{"class":68},[32,2769,1396],{"class":90},[32,2771,2772],{"class":159},">\"",[32,2774,100],{"class":68},[32,2776,2777],{"class":34,"line":116},[32,2778,58],{"emptyLinePlaceholder":57},[32,2780,2781,2784,2787,2789,2791,2793,2796,2798,2800,2802,2805,2807,2809,2812],{"class":34,"line":129},[32,2782,2783],{"class":68},"create_user({",[32,2785,2786],{"class":159},"\"id\"",[32,2788,215],{"class":68},[32,2790,725],{"class":90},[32,2792,163],{"class":68},[32,2794,2795],{"class":159},"\"name\"",[32,2797,215],{"class":68},[32,2799,745],{"class":159},[32,2801,163],{"class":68},[32,2803,2804],{"class":159},"\"email\"",[32,2806,215],{"class":68},[32,2808,735],{"class":159},[32,2810,2811],{"class":68},"})   ",[32,2813,2814],{"class":38},"# OK\n",[32,2816,2817],{"class":34,"line":134},[32,2818,2819],{"class":38},"# create_user({\"id\": 1, \"name\": \"Ada\"})   # mypy error: Missing key \"email\"\n",[32,2821,2822],{"class":34,"line":140},[32,2823,58],{"emptyLinePlaceholder":57},[32,2825,2826,2828,2831,2833,2835],{"class":34,"line":146},[32,2827,149],{"class":64},[32,2829,2830],{"class":152}," UserRecordPartial",[32,2832,285],{"class":68},[32,2834,288],{"class":152},[32,2836,291],{"class":68},[32,2838,2839,2841,2843],{"class":34,"line":179},[32,2840,297],{"class":90},[32,2842,215],{"class":68},[32,2844,302],{"class":90},[32,2846,2847,2849],{"class":34,"line":185},[32,2848,317],{"class":68},[32,2850,311],{"class":90},[32,2852,2853,2856,2858,2860],{"class":34,"line":191},[32,2854,2855],{"class":68},"    nickname: NotRequired[",[32,2857,328],{"class":90},[32,2859,1511],{"class":68},[32,2861,2862],{"class":38},"# 3.11+ — this key may be omitted entirely\n",[32,2864,2865],{"class":34,"line":221},[32,2866,58],{"emptyLinePlaceholder":57},[32,2868,2869,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2893],{"class":34,"line":226},[32,2870,2871],{"class":68},"create_user_partial: UserRecordPartial ",[32,2873,170],{"class":64},[32,2875,1276],{"class":68},[32,2877,2786],{"class":159},[32,2879,215],{"class":68},[32,2881,725],{"class":90},[32,2883,163],{"class":68},[32,2885,2795],{"class":159},[32,2887,215],{"class":68},[32,2889,745],{"class":159},[32,2891,2892],{"class":68},"}   ",[32,2894,2895],{"class":38},"# OK — nickname is optional\n",[1412,2897,2898,2900,2901,2904,2905,2908],{},[29,2899,288],{}," is purely a static-typing construct — at runtime, a ",[29,2902,2903],{},"UserRecord"," IS just a plain ",[29,2906,2907],{},"dict",", with no validation, no special class, and no enforcement that required keys are present:",[19,2910,2911],{"language":21},[23,2912,2914],{"className":25,"code":2913,"language":21,"meta":27,"style":27},"bad_user: UserRecord = {\"id\": \"not an int!\", \"name\": 123, \"email\": None}   # mypy flags ALL three\nprint(type(bad_user))    # \u003Cclass 'dict'> — runtime doesn't care, TypedDict vanishes completely at runtime\nprint(bad_user)             # {'id': 'not an int!', 'name': 123, 'email': None} — runs fine, no exception\n",[29,2915,2916,2954,2969],{"__ignoreMap":27},[32,2917,2918,2921,2923,2925,2927,2929,2932,2934,2936,2938,2941,2943,2945,2947,2949,2951],{"class":34,"line":35},[32,2919,2920],{"class":68},"bad_user: UserRecord ",[32,2922,170],{"class":64},[32,2924,1276],{"class":68},[32,2926,2786],{"class":159},[32,2928,215],{"class":68},[32,2930,2931],{"class":159},"\"not an int!\"",[32,2933,163],{"class":68},[32,2935,2795],{"class":159},[32,2937,215],{"class":68},[32,2939,2940],{"class":90},"123",[32,2942,163],{"class":68},[32,2944,2804],{"class":159},[32,2946,215],{"class":68},[32,2948,507],{"class":90},[32,2950,2892],{"class":68},[32,2952,2953],{"class":38},"# mypy flags ALL three\n",[32,2955,2956,2958,2960,2963,2966],{"class":34,"line":42},[32,2957,2503],{"class":90},[32,2959,285],{"class":68},[32,2961,2962],{"class":90},"type",[32,2964,2965],{"class":68},"(bad_user))    ",[32,2967,2968],{"class":38},"# \u003Cclass 'dict'> — runtime doesn't care, TypedDict vanishes completely at runtime\n",[32,2970,2971,2973,2976],{"class":34,"line":48},[32,2972,2503],{"class":90},[32,2974,2975],{"class":68},"(bad_user)             ",[32,2977,2978],{"class":38},"# {'id': 'not an int!', 'name': 123, 'email': None} — runs fine, no exception\n",[1412,2980,2981,2982,2985,2986,2988,2989,2991],{},"For actual runtime validation of shapes like this (not just static hints), reach for a library like ",[29,2983,2984],{},"pydantic",", which does enforce and coerce types when data is constructed — ",[29,2987,288],{}," alone gives you IDE autocomplete and ",[29,2990,2347],{}," checking only.",[14,2993,2995,163,2998,3001,3002],{"id":2994},"literal-final-and-any",[29,2996,2997],{},"Literal",[29,2999,3000],{},"Final",", and ",[29,3003,3004],{},"Any",[19,3006,3007],{"language":21},[23,3008,3010],{"className":25,"code":3009,"language":21,"meta":27,"style":27},"from typing import Literal, Final, Any\n\ndef set_mode(mode: Literal[\"read\", \"write\", \"append\"]) -> None:\n    print(f\"Mode set to {mode}\")\n\nset_mode(\"read\")     # OK\n# set_mode(\"delete\")   # mypy error: Argument has incompatible type; expected one of the Literal values\n\nMAX_RETRIES: Final = 3       # mypy flags any later reassignment of MAX_RETRIES as an error\n# MAX_RETRIES = 5             # mypy error: Cannot assign to final name \"MAX_RETRIES\"\n\ndef accept_anything(value: Any) -> Any:   # Any OPTS OUT of type checking entirely for this value\n    return value.whatever_method_i_want()    # mypy will NOT flag this, even if it's nonsense\n",[29,3011,3012,3023,3027,3056,3078,3082,3093,3098,3102,3118,3123,3127,3140],{"__ignoreMap":27},[32,3013,3014,3016,3018,3020],{"class":34,"line":35},[32,3015,65],{"class":64},[32,3017,69],{"class":68},[32,3019,72],{"class":64},[32,3021,3022],{"class":68}," Literal, Final, Any\n",[32,3024,3025],{"class":34,"line":42},[32,3026,58],{"emptyLinePlaceholder":57},[32,3028,3029,3031,3034,3037,3040,3042,3045,3047,3050,3052,3054],{"class":34,"line":48},[32,3030,901],{"class":64},[32,3032,3033],{"class":152}," set_mode",[32,3035,3036],{"class":68},"(mode: Literal[",[32,3038,3039],{"class":159},"\"read\"",[32,3041,163],{"class":68},[32,3043,3044],{"class":159},"\"write\"",[32,3046,163],{"class":68},[32,3048,3049],{"class":159},"\"append\"",[32,3051,1316],{"class":68},[32,3053,507],{"class":90},[32,3055,423],{"class":68},[32,3057,3058,3060,3062,3064,3067,3069,3072,3074,3076],{"class":34,"line":54},[32,3059,787],{"class":90},[32,3061,285],{"class":68},[32,3063,1384],{"class":64},[32,3065,3066],{"class":159},"\"Mode set to ",[32,3068,1390],{"class":90},[32,3070,3071],{"class":68},"mode",[32,3073,1396],{"class":90},[32,3075,1408],{"class":159},[32,3077,100],{"class":68},[32,3079,3080],{"class":34,"line":61},[32,3081,58],{"emptyLinePlaceholder":57},[32,3083,3084,3087,3089,3091],{"class":34,"line":78},[32,3085,3086],{"class":68},"set_mode(",[32,3088,3039],{"class":159},[32,3090,1165],{"class":68},[32,3092,2814],{"class":38},[32,3094,3095],{"class":34,"line":84},[32,3096,3097],{"class":38},"# set_mode(\"delete\")   # mypy error: Argument has incompatible type; expected one of the Literal values\n",[32,3099,3100],{"class":34,"line":97},[32,3101,58],{"emptyLinePlaceholder":57},[32,3103,3104,3107,3110,3112,3115],{"class":34,"line":103},[32,3105,3106],{"class":90},"MAX_RETRIES",[32,3108,3109],{"class":68},": Final ",[32,3111,170],{"class":64},[32,3113,3114],{"class":90}," 3",[32,3116,3117],{"class":38},"       # mypy flags any later reassignment of MAX_RETRIES as an error\n",[32,3119,3120],{"class":34,"line":116},[32,3121,3122],{"class":38},"# MAX_RETRIES = 5             # mypy error: Cannot assign to final name \"MAX_RETRIES\"\n",[32,3124,3125],{"class":34,"line":129},[32,3126,58],{"emptyLinePlaceholder":57},[32,3128,3129,3131,3134,3137],{"class":34,"line":134},[32,3130,901],{"class":64},[32,3132,3133],{"class":152}," accept_anything",[32,3135,3136],{"class":68},"(value: Any) -> Any:   ",[32,3138,3139],{"class":38},"# Any OPTS OUT of type checking entirely for this value\n",[32,3141,3142,3144,3147],{"class":34,"line":140},[32,3143,932],{"class":64},[32,3145,3146],{"class":68}," value.whatever_method_i_want()    ",[32,3148,3149],{"class":38},"# mypy will NOT flag this, even if it's nonsense\n",[1412,3151,3152,3154,3155,3157,3158,3160,3161,3164,3165,3167,3168,3170,3171,3173],{},[29,3153,3004],{}," is the type-checking escape hatch — it is compatible with every other type in both directions, meaning assigning an ",[29,3156,3004],{},"-typed value to an ",[29,3159,203],{}," variable, or vice versa, is never flagged. ",[1702,3162,3163],{},"Best practice",": use ",[29,3166,3004],{}," sparingly and explicitly (e.g., for genuinely dynamic data like raw JSON) rather than as a shortcut to silence errors — overusing ",[29,3169,3004],{}," quietly disables the type checker across your whole call graph, since ",[29,3172,3004],{}," propagates through any expression it touches.",[14,3175,3177,3178],{"id":3176},"callable-type-aliases-and-type_checking","Callable, Type Aliases, and ",[29,3179,91],{},[19,3181,3182],{"language":21},[23,3183,3185],{"className":25,"code":3184,"language":21,"meta":27,"style":27},"from typing import Callable, TYPE_CHECKING\n\ndef apply_twice(fn: Callable[[int], int], value: int) -> int:   # Callable[[ArgTypes], ReturnType]\n    return fn(fn(value))\n\nprint(apply_twice(lambda x: x * 2, 5))   # 20\n\nHandler = Callable[[str, int], None]     # a TYPE ALIAS — just a name for a complex type, for readability\n\ndef register(event: str, priority: int) -> None: ...\nhandlers: list[Handler] = [register]\n\nif TYPE_CHECKING:            # this block NEVER executes at runtime — imports here are type-checker-only\n    from mymodule import HeavyExpensiveClass   # avoids a real import cost \u002F circular import at runtime\n\ndef process(item: \"HeavyExpensiveClass\") -> None:   # forward reference as a STRING — resolved only by mypy\n    ...\n",[29,3186,3187,3201,3205,3236,3243,3247,3275,3279,3304,3308,3333,3343,3347,3359,3374,3378,3398],{"__ignoreMap":27},[32,3188,3189,3191,3193,3195,3198],{"class":34,"line":35},[32,3190,65],{"class":64},[32,3192,69],{"class":68},[32,3194,72],{"class":64},[32,3196,3197],{"class":68}," Callable, ",[32,3199,3200],{"class":90},"TYPE_CHECKING\n",[32,3202,3203],{"class":34,"line":42},[32,3204,58],{"emptyLinePlaceholder":57},[32,3206,3207,3209,3212,3215,3217,3220,3222,3225,3227,3229,3231,3233],{"class":34,"line":48},[32,3208,901],{"class":64},[32,3210,3211],{"class":152}," apply_twice",[32,3213,3214],{"class":68},"(fn: Callable[[",[32,3216,203],{"class":90},[32,3218,3219],{"class":68},"], ",[32,3221,203],{"class":90},[32,3223,3224],{"class":68},"], value: ",[32,3226,203],{"class":90},[32,3228,256],{"class":68},[32,3230,203],{"class":90},[32,3232,1372],{"class":68},[32,3234,3235],{"class":38},"# Callable[[ArgTypes], ReturnType]\n",[32,3237,3238,3240],{"class":34,"line":54},[32,3239,932],{"class":64},[32,3241,3242],{"class":68}," fn(fn(value))\n",[32,3244,3245],{"class":34,"line":61},[32,3246,58],{"emptyLinePlaceholder":57},[32,3248,3249,3251,3254,3257,3260,3262,3265,3267,3269,3272],{"class":34,"line":78},[32,3250,2503],{"class":90},[32,3252,3253],{"class":68},"(apply_twice(",[32,3255,3256],{"class":64},"lambda",[32,3258,3259],{"class":68}," x: x ",[32,3261,2275],{"class":64},[32,3263,3264],{"class":90}," 2",[32,3266,163],{"class":68},[32,3268,2080],{"class":90},[32,3270,3271],{"class":68},"))   ",[32,3273,3274],{"class":38},"# 20\n",[32,3276,3277],{"class":34,"line":84},[32,3278,58],{"emptyLinePlaceholder":57},[32,3280,3281,3284,3286,3289,3291,3293,3295,3297,3299,3301],{"class":34,"line":97},[32,3282,3283],{"class":68},"Handler ",[32,3285,170],{"class":64},[32,3287,3288],{"class":68}," Callable[[",[32,3290,328],{"class":90},[32,3292,163],{"class":68},[32,3294,203],{"class":90},[32,3296,3219],{"class":68},[32,3298,507],{"class":90},[32,3300,1511],{"class":68},[32,3302,3303],{"class":38},"# a TYPE ALIAS — just a name for a complex type, for readability\n",[32,3305,3306],{"class":34,"line":103},[32,3307,58],{"emptyLinePlaceholder":57},[32,3309,3310,3312,3315,3318,3320,3323,3325,3327,3329,3331],{"class":34,"line":116},[32,3311,901],{"class":64},[32,3313,3314],{"class":152}," register",[32,3316,3317],{"class":68},"(event: ",[32,3319,328],{"class":90},[32,3321,3322],{"class":68},", priority: ",[32,3324,203],{"class":90},[32,3326,256],{"class":68},[32,3328,507],{"class":90},[32,3330,215],{"class":68},[32,3332,218],{"class":90},[32,3334,3335,3338,3340],{"class":34,"line":129},[32,3336,3337],{"class":68},"handlers: list[Handler] ",[32,3339,170],{"class":64},[32,3341,3342],{"class":68}," [register]\n",[32,3344,3345],{"class":34,"line":134},[32,3346,58],{"emptyLinePlaceholder":57},[32,3348,3349,3351,3353,3356],{"class":34,"line":140},[32,3350,778],{"class":64},[32,3352,858],{"class":90},[32,3354,3355],{"class":68},":            ",[32,3357,3358],{"class":38},"# this block NEVER executes at runtime — imports here are type-checker-only\n",[32,3360,3361,3363,3366,3368,3371],{"class":34,"line":146},[32,3362,881],{"class":64},[32,3364,3365],{"class":68}," mymodule ",[32,3367,72],{"class":64},[32,3369,3370],{"class":68}," HeavyExpensiveClass   ",[32,3372,3373],{"class":38},"# avoids a real import cost \u002F circular import at runtime\n",[32,3375,3376],{"class":34,"line":179},[32,3377,58],{"emptyLinePlaceholder":57},[32,3379,3380,3382,3384,3386,3389,3391,3393,3395],{"class":34,"line":185},[32,3381,901],{"class":64},[32,3383,904],{"class":152},[32,3385,907],{"class":68},[32,3387,3388],{"class":159},"\"HeavyExpensiveClass\"",[32,3390,256],{"class":68},[32,3392,507],{"class":90},[32,3394,1372],{"class":68},[32,3396,3397],{"class":38},"# forward reference as a STRING — resolved only by mypy\n",[32,3399,3400],{"class":34,"line":191},[32,3401,3402],{"class":90},"    ...\n",[1412,3404,3405,3407,3408,3411,3412,3415],{},[29,3406,91],{}," is ",[29,3409,3410],{},"False"," at runtime and ",[29,3413,3414],{},"True"," only from a static checker's perspective — it's the standard pattern for importing something purely for annotations (avoiding a circular import, or an expensive import that's never actually needed at runtime because the annotation is never evaluated as real code).",[14,3417,3419,3420,3422],{"id":3418},"running-mypy-static-checking-in-practice","Running ",[29,3421,2347],{},": Static Checking in Practice",[19,3424,3426],{"language":3425},"bash",[23,3427,3430],{"className":3428,"code":3429,"language":3425,"meta":27,"style":27},"language-bash shiki shiki-themes github-light github-dark","pip install mypy\nmypy myscript.py\n\n# myscript.py:12: error: Argument 1 to \"greet\" has incompatible type \"int\"; expected \"str\"  [arg-type]\n# Found 1 error in 1 file (checked 1 source file)\n",[29,3431,3432,3443,3450,3454,3459],{"__ignoreMap":27},[32,3433,3434,3437,3440],{"class":34,"line":35},[32,3435,3436],{"class":152},"pip",[32,3438,3439],{"class":159}," install",[32,3441,3442],{"class":159}," mypy\n",[32,3444,3445,3447],{"class":34,"line":42},[32,3446,2347],{"class":152},[32,3448,3449],{"class":159}," myscript.py\n",[32,3451,3452],{"class":34,"line":48},[32,3453,58],{"emptyLinePlaceholder":57},[32,3455,3456],{"class":34,"line":54},[32,3457,3458],{"class":38},"# myscript.py:12: error: Argument 1 to \"greet\" has incompatible type \"int\"; expected \"str\"  [arg-type]\n",[32,3460,3461],{"class":34,"line":61},[32,3462,3463],{"class":38},"# Found 1 error in 1 file (checked 1 source file)\n",[19,3465,3467],{"language":3466},"ini",[23,3468,3471],{"className":3469,"code":3470,"language":3466,"meta":27,"style":27},"language-ini shiki shiki-themes github-light github-dark","[mypy]\npython_version = 3.12\ndisallow_untyped_defs = true\nwarn_return_any = true\nwarn_unused_ignores = true\nstrict = false\n",[29,3472,3473,3478,3486,3494,3501,3508],{"__ignoreMap":27},[32,3474,3475],{"class":34,"line":35},[32,3476,3477],{"class":152},"[mypy]\n",[32,3479,3480,3483],{"class":34,"line":42},[32,3481,3482],{"class":64},"python_version",[32,3484,3485],{"class":68}," = 3.12\n",[32,3487,3488,3491],{"class":34,"line":48},[32,3489,3490],{"class":64},"disallow_untyped_defs",[32,3492,3493],{"class":68}," = true\n",[32,3495,3496,3499],{"class":34,"line":54},[32,3497,3498],{"class":64},"warn_return_any",[32,3500,3493],{"class":68},[32,3502,3503,3506],{"class":34,"line":61},[32,3504,3505],{"class":64},"warn_unused_ignores",[32,3507,3493],{"class":68},[32,3509,3510,3513],{"class":34,"line":78},[32,3511,3512],{"class":64},"strict",[32,3514,3515],{"class":68}," = false\n",[1412,3517,3518,3521,3522,3525,3526,3529],{},[29,3519,3520],{},"disallow_untyped_defs = true"," is the single highest-value setting for adopting typing in an existing codebase — it forces every function to have annotations, catching the common failure mode of typing ",[2547,3523,3524],{},"some"," functions and leaving critical ones silently unchecked. Ratchet up to ",[29,3527,3528],{},"strict = true"," (which bundles a dozen stricter flags) once a codebase is mostly annotated.",[19,3531,3532],{"language":21},[23,3533,3535],{"className":25,"code":3534,"language":21,"meta":27,"style":27},"def risky_operation(value) -> int:      # UNANNOTATED parameter — mypy treats `value` as implicit Any\n    return value + 1                      # no error reported, even if `value` is later called with a str!\n\nresult = risky_operation(\"not a number\")   # mypy: no error (value: Any) — but this raises TypeError at RUNTIME\n",[29,3536,3537,3555,3568,3572],{"__ignoreMap":27},[32,3538,3539,3541,3544,3547,3549,3552],{"class":34,"line":35},[32,3540,901],{"class":64},[32,3542,3543],{"class":152}," risky_operation",[32,3545,3546],{"class":68},"(value) -> ",[32,3548,203],{"class":90},[32,3550,3551],{"class":68},":      ",[32,3553,3554],{"class":38},"# UNANNOTATED parameter — mypy treats `value` as implicit Any\n",[32,3556,3557,3559,3561,3563,3565],{"class":34,"line":42},[32,3558,932],{"class":64},[32,3560,2272],{"class":68},[32,3562,2331],{"class":64},[32,3564,1365],{"class":90},[32,3566,3567],{"class":38},"                      # no error reported, even if `value` is later called with a str!\n",[32,3569,3570],{"class":34,"line":48},[32,3571,58],{"emptyLinePlaceholder":57},[32,3573,3574,3577,3579,3582,3585,3587],{"class":34,"line":54},[32,3575,3576],{"class":68},"result ",[32,3578,170],{"class":64},[32,3580,3581],{"class":68}," risky_operation(",[32,3583,3584],{"class":159},"\"not a number\"",[32,3586,769],{"class":68},[32,3588,3589],{"class":38},"# mypy: no error (value: Any) — but this raises TypeError at RUNTIME\n",[1412,3591,3592,3593,3595,3596,3598],{},"An unannotated parameter defaults to ",[29,3594,3004],{}," under normal settings, which silently defeats type checking for that parameter — ",[29,3597,3490],{}," exists specifically to catch this gap by making every def require full annotations before mypy will pass.",[14,3600,3602],{"id":3601},"tips-tricks","💡 Tips & Tricks",[3604,3605,3606,3624,3647,3659,3668],"ul",{},[3607,3608,3609,3612,3613,3616,3617,3620,3621,3623],"li",{},[1702,3610,3611],{},"Idiom",": annotate function signatures first, and only add variable-level annotations (",[29,3614,3615],{},"x: int = 5",") where the inferred type would otherwise be ambiguous (e.g., an empty list ",[29,3618,3619],{},"items: list[str] = []",") — annotating every trivial local variable is noise ",[29,3622,2347],{}," doesn't need.",[3607,3625,3626,215,3629,3632,3633,3635,3636,3638,3639,3642,3643,3646],{},[1702,3627,3628],{},"Debug",[29,3630,3631],{},"reveal_type(x)"," is a special ",[29,3634,2347],{},"-only pseudo-function — insert it anywhere to have ",[29,3637,2347],{}," print its inferred type for ",[29,3640,3641],{},"x"," in the error output, then delete it; it's not a real function and will ",[29,3644,3645],{},"NameError"," if actually run.",[3607,3648,3649,3164,3651,3654,3655,3658],{},[1702,3650,3611],{},[29,3652,3653],{},"from __future__ import annotations"," (or rely on it being default behavior in newer Python) to make all annotations lazily-evaluated strings, which lets you reference a class in its own methods' hints (",[29,3656,3657],{},"def clone(self) -> \"MyClass\"",") without the quotes.",[3607,3660,3661,3664,3665,3667],{},[1702,3662,3663],{},"Performance",": since annotations are never evaluated at runtime under ",[29,3666,3653],{},", expensive or forward-referenced types in hints impose zero runtime cost — a good reason to type liberally without performance anxiety.",[3607,3669,3670,215,3673,3675,3676,3679,3680,3682],{},[1702,3671,3672],{},"Safety",[29,3674,2354],{}," classes are the correct tool for typing \"duck typed\" parameters (loggers, anything with a ",[29,3677,3678],{},".write()"," method, etc.) instead of ",[29,3681,3004],{}," — you get real static checking without forcing every caller to inherit from a common base class.",[14,3684,3686],{"id":3685},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[3604,3688,3689,3706,3727,3745,3760],{},[3607,3690,3691,3694,3695,3698,3699,3702,3703,3705],{},[1702,3692,3693],{},"Type hints are never checked at runtime by the interpreter itself"," — ",[29,3696,3697],{},"def f(x: int): ..."," happily accepts ",[29,3700,3701],{},"f(\"a string\")"," and runs to completion (or fails later, for unrelated reasons); only a separate tool like ",[29,3704,2347],{}," catches the mismatch, and only if it's actually run as part of CI.",[3607,3707,3708,3715,3716,3719,3720,3722,3723,3726],{},[1702,3709,3710,3712,3713],{},[29,3711,1695],{}," does not supply a default value of ",[29,3714,507],{}," — it only widens the accepted type to ",[29,3717,3718],{},"X | None","; forgetting the separate ",[29,3721,1714],{}," default still makes the parameter required to pass, producing a ",[29,3724,3725],{},"TypeError: missing required argument",", not a helpful type error.",[3607,3728,3729,3734,3735,3737,3738,3740,3741,3744],{},[1702,3730,3731,3733],{},[29,3732,288],{}," provides zero runtime validation"," — a dict missing required keys, or with wrong-typed values, is only ever flagged by ",[29,3736,2347],{},"; at runtime it's an ordinary ",[29,3739,2907],{}," that will raise a plain ",[29,3742,3743],{},"KeyError"," (not a validation error) the first time a missing key is accessed.",[3607,3746,3747,3756,3757,3759],{},[1702,3748,3749,3750,3752,3753,3755],{},"An unannotated function parameter is implicitly ",[29,3751,3004],{}," under default ",[29,3754,2347],{}," settings",", silently opting that parameter out of type checking — a partially-annotated codebase can look \"typed\" while large gaps go completely unchecked; ",[29,3758,3490],{}," closes this hole.",[3607,3761,3762,3767,3768,3770,3771,3773],{},[1702,3763,3764,3766],{},[29,3765,3004],{}," is bidirectionally compatible with everything and propagates through expressions"," — one ",[29,3769,3004],{},"-typed value flowing into an otherwise fully-typed function can silence type errors for everything downstream that touches it, which is why overuse of ",[29,3772,3004],{}," is often worse than not typing at all (it creates false confidence).",[14,3775,3777],{"id":3776},"spot-the-bug","🧠 Spot the Bug",[1412,3779,3780,3781,3783],{},"A function is meant to look up a user by ID and return their display name, or a fallback if not found. ",[29,3782,2347],{}," reports no errors, but it crashes in production. Find the bug.",[19,3785,3786],{"language":21},[23,3787,3789],{"className":25,"code":3788,"language":21,"meta":27,"style":27},"from typing import Optional\n\ndef get_display_name(user_id: int, users: dict[int, str]) -> str:\n    name: Optional[str] = users.get(user_id)\n    return name.upper()\n\nprint(get_display_name(1, {1: \"ada\"}))     # \"ADA\"\nprint(get_display_name(2, {1: \"ada\"}))       # crashes\n",[29,3790,3791,3802,3806,3832,3846,3853,3857,3882],{"__ignoreMap":27},[32,3792,3793,3795,3797,3799],{"class":34,"line":35},[32,3794,65],{"class":64},[32,3796,69],{"class":68},[32,3798,72],{"class":64},[32,3800,3801],{"class":68}," Optional\n",[32,3803,3804],{"class":34,"line":42},[32,3805,58],{"emptyLinePlaceholder":57},[32,3807,3808,3810,3813,3815,3817,3820,3822,3824,3826,3828,3830],{"class":34,"line":48},[32,3809,901],{"class":64},[32,3811,3812],{"class":152}," get_display_name",[32,3814,1557],{"class":68},[32,3816,203],{"class":90},[32,3818,3819],{"class":68},", users: dict[",[32,3821,203],{"class":90},[32,3823,163],{"class":68},[32,3825,328],{"class":90},[32,3827,1316],{"class":68},[32,3829,328],{"class":90},[32,3831,423],{"class":68},[32,3833,3834,3837,3839,3841,3843],{"class":34,"line":54},[32,3835,3836],{"class":68},"    name: Optional[",[32,3838,328],{"class":90},[32,3840,960],{"class":68},[32,3842,170],{"class":64},[32,3844,3845],{"class":68}," users.get(user_id)\n",[32,3847,3848,3850],{"class":34,"line":61},[32,3849,932],{"class":64},[32,3851,3852],{"class":68}," name.upper()\n",[32,3854,3855],{"class":34,"line":78},[32,3856,58],{"emptyLinePlaceholder":57},[32,3858,3859,3861,3864,3866,3869,3871,3873,3876,3879],{"class":34,"line":84},[32,3860,2503],{"class":90},[32,3862,3863],{"class":68},"(get_display_name(",[32,3865,725],{"class":90},[32,3867,3868],{"class":68},", {",[32,3870,725],{"class":90},[32,3872,215],{"class":68},[32,3874,3875],{"class":159},"\"ada\"",[32,3877,3878],{"class":68},"}))     ",[32,3880,3881],{"class":38},"# \"ADA\"\n",[32,3883,3884,3886,3888,3890,3892,3894,3896,3898,3901],{"class":34,"line":97},[32,3885,2503],{"class":90},[32,3887,3863],{"class":68},[32,3889,1293],{"class":90},[32,3891,3868],{"class":68},[32,3893,725],{"class":90},[32,3895,215],{"class":68},[32,3897,3875],{"class":159},[32,3899,3900],{"class":68},"}))       ",[32,3902,3903],{"class":38},"# crashes\n",[3905,3906,3907,3911,3971,3980,4047],"details",{},[3908,3909,3910],"summary",{},"Answer",[1412,3912,3913,3916,3917,3919,3920,3922,3923,3925,3926,3929,3930,3933,3934,3936,3937,3939,3940,3942,3943,3945,3946,3949,3950,3953,3954,3956,3957,3916,3960,3962,3963,3966,3967,3970],{},[29,3914,3915],{},"dict.get(user_id)"," returns ",[29,3918,507],{}," when the key is missing — that's exactly why ",[29,3921,740],{}," is correctly annotated ",[29,3924,1707],{},". But the very next line, ",[29,3927,3928],{},"name.upper()",", calls ",[29,3931,3932],{},".upper()"," unconditionally on ",[29,3935,740],{},", without ever checking for ",[29,3938,507],{}," first. If ",[29,3941,2347],{}," is run with default (non-strict) settings, this specific pattern can slip through depending on configuration and mypy version, but under proper strict settings (",[29,3944,3528],{}," or at minimum ",[29,3947,3948],{},"--strict-optional",", which is on by default in modern mypy), this SHOULD be flagged as ",[29,3951,3952],{},"error: Item \"None\" of \"Optional[str]\" has no attribute \"upper\"",". The scenario here is the common real failure mode: the type checker isn't run at all in CI, or is run with settings loose enough to miss it, so the ",[29,3955,1520],{}," annotation was correctly written but never actually enforced before deployment — ",[29,3958,3959],{},"users.get(2)",[29,3961,507],{}," at runtime, and ",[29,3964,3965],{},"None.upper()"," raises ",[29,3968,3969],{},"AttributeError: 'NoneType' object has no attribute 'upper'",".",[1412,3972,3973,3974,3976,3977,3979],{},"The fix handles the ",[29,3975,507],{}," case explicitly, which is the entire point of marking something ",[29,3978,1520],{}," in the first place:",[19,3981,3982],{"language":21},[23,3983,3985],{"className":25,"code":3984,"language":21,"meta":27,"style":27},"def get_display_name(user_id: int, users: dict[int, str]) -> str:\n    name = users.get(user_id)\n    if name is None:\n        return \"Unknown User\"\n    return name.upper()\n",[29,3986,3987,4011,4020,4034,4041],{"__ignoreMap":27},[32,3988,3989,3991,3993,3995,3997,3999,4001,4003,4005,4007,4009],{"class":34,"line":35},[32,3990,901],{"class":64},[32,3992,3812],{"class":152},[32,3994,1557],{"class":68},[32,3996,203],{"class":90},[32,3998,3819],{"class":68},[32,4000,203],{"class":90},[32,4002,163],{"class":68},[32,4004,328],{"class":90},[32,4006,1316],{"class":68},[32,4008,328],{"class":90},[32,4010,423],{"class":68},[32,4012,4013,4016,4018],{"class":34,"line":42},[32,4014,4015],{"class":68},"    name ",[32,4017,170],{"class":64},[32,4019,3845],{"class":68},[32,4021,4022,4025,4028,4030,4032],{"class":34,"line":48},[32,4023,4024],{"class":64},"    if",[32,4026,4027],{"class":68}," name ",[32,4029,652],{"class":64},[32,4031,212],{"class":90},[32,4033,423],{"class":68},[32,4035,4036,4038],{"class":34,"line":54},[32,4037,558],{"class":64},[32,4039,4040],{"class":159}," \"Unknown User\"\n",[32,4042,4043,4045],{"class":34,"line":61},[32,4044,932],{"class":64},[32,4046,3852],{"class":68},[1412,4048,4049,4052,4053,4055,4056,4058,4059,4061],{},[1702,4050,4051],{},"The lesson",": an ",[29,4054,1695],{}," annotation is a promise that must be honored with an actual ",[29,4057,507],{}," check in the code — it documents the possibility of ",[29,4060,507],{}," but does nothing to prevent a missed check unless a type checker is actually run, in strict mode, as a required CI gate rather than an optional local habit.",[14,4063,4065],{"id":4064},"key-takeaways","Key Takeaways",[3604,4067,4068,4078,4091,4099,4110,4115],{},[3607,4069,4070,4071,4073,4074,4077],{},"Type hints are purely static-analysis metadata — the Python interpreter never checks them at runtime; only a separate tool like ",[29,4072,2347],{}," or ",[29,4075,4076],{},"pyright",", run explicitly, catches mismatches.",[3607,4079,4080,4082,4083,4085,4086,4088,4089,3970],{},[29,4081,1695],{}," means ",[29,4084,3718],{},", not \"has a default value\" — the ",[29,4087,1714],{}," default must be written separately even when the parameter is ",[29,4090,1520],{},[3607,4092,4093,4095,4096,4098],{},[29,4094,2354],{}," gives you statically-checked structural typing (duck typing) with no inheritance required, in contrast to ",[29,4097,2525],{},", which requires explicit subclassing to satisfy an interface.",[3607,4100,4101,4103,4104,4106,4107,4109],{},[29,4102,288],{}," documents a dict's expected shape for ",[29,4105,2347],{}," and editors, but provides zero runtime validation — reach for ",[29,4108,2984],{}," if you need actual runtime enforcement.",[3607,4111,4112,4114],{},[29,4113,3004],{}," is compatible with everything in both directions and propagates through expressions — overusing it creates false confidence by silently disabling checks across everything it touches.",[3607,4116,4117,4118,4120,4121,4123,4124,4126,4127,4129],{},"Enable ",[29,4119,3490],{}," (and eventually ",[29,4122,3528],{},") in ",[29,4125,2347],{}," configuration — unannotated functions default to ",[29,4128,3004],{}," parameters, which silently defeats type checking for exactly the code most likely to have a bug.",[4131,4132,4133],"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 .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 .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}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":4135},[4136,4137,4138,4140,4144,4146,4148,4150,4152,4154,4155,4156,4157],{"id":16,"depth":42,"text":17},{"id":1193,"depth":42,"text":1194},{"id":1517,"depth":42,"text":4139},"Optional and Union: Values That Can Be More Than One Type",{"id":1848,"depth":42,"text":4141,"children":4142},"Generics: TypeVar and Generic Classes\u002FFunctions",[4143],{"id":2203,"depth":48,"text":2204},{"id":2351,"depth":42,"text":4145},"Protocol: Structural Typing (Duck Typing, Statically Checked)",{"id":2655,"depth":42,"text":4147},"TypedDict: Typed Dictionary Shapes",{"id":2994,"depth":42,"text":4149},"Literal, Final, and Any",{"id":3176,"depth":42,"text":4151},"Callable, Type Aliases, and TYPE_CHECKING",{"id":3418,"depth":42,"text":4153},"Running mypy: Static Checking in Practice",{"id":3601,"depth":42,"text":3602},{"id":3685,"depth":42,"text":3686},{"id":3776,"depth":42,"text":3777},{"id":4064,"depth":42,"text":4065},"md",{},"\u002Fpython\u002F20-type-hints-and-typing",{"title":5,"description":27},"python\u002F20-type-hints-and-typing","PfMW3ts_8q6UUBGWchyVOxbxD4JxPwPrRtKSTKUBIhw",1789924651673]