[{"data":1,"prerenderedAt":970},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-generics-protocols-explained":3},{"id":4,"title":5,"body":6,"description":956,"difficulty":957,"extension":958,"framework":959,"frameworkSlug":44,"meta":960,"navigation":72,"order":69,"path":961,"qaPath":962,"seo":963,"stem":964,"subtopic":965,"topic":966,"topicSlug":967,"updated":968,"__hash__":969},"blog\u002Fblog\u002Fpython-generics-protocols-explained.md","Python Generics & Protocols Explained — TypeVar, Generic Classes, and Structural Typing",{"type":7,"value":8,"toc":946},"minimark",[9,14,23,27,39,181,190,194,201,278,281,285,292,465,480,484,495,636,639,643,654,780,794,798,809,886,889,893,942],[10,11,13],"h2",{"id":12},"python-generics-protocols-explained","Python generics & protocols, explained",[15,16,17,18,22],"p",{},"Type hints get powerful when you make them ",[19,20,21],"strong",{},"reusable",". Generics let one function or class\nwork over many types without losing type information, and Protocols bring static checking to\nPython's duck typing. Together they let you type real, flexible code precisely.",[10,24,26],{"id":25},"the-problem-generics-solve","The problem generics solve",[15,28,29,30,34,35,38],{},"Without generics, a function that returns one of its arguments either loses the type (using\n",[31,32,33],"code",{},"Any",") or must be written per-type. A ",[19,36,37],{},"type variable"," preserves the relationship between\ninput and output.",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-python shiki shiki-themes github-light github-dark","from typing import TypeVar\n\nT = TypeVar(\"T\")\n\ndef first(items: list[T]) -> T:      # output type matches the list's element type\n    return items[0]\n\nfirst([1, 2, 3])        # type checker knows this is int\nfirst([\"a\", \"b\"])       # ...and this is str\n","python","",[31,47,48,67,74,93,98,115,131,136,162],{"__ignoreMap":45},[49,50,53,57,61,64],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"szBVR","from",[49,58,60],{"class":59},"sVt8B"," typing ",[49,62,63],{"class":55},"import",[49,65,66],{"class":59}," TypeVar\n",[49,68,70],{"class":51,"line":69},2,[49,71,73],{"emptyLinePlaceholder":72},true,"\n",[49,75,77,80,83,86,90],{"class":51,"line":76},3,[49,78,79],{"class":59},"T ",[49,81,82],{"class":55},"=",[49,84,85],{"class":59}," TypeVar(",[49,87,89],{"class":88},"sZZnC","\"T\"",[49,91,92],{"class":59},")\n",[49,94,96],{"class":51,"line":95},4,[49,97,73],{"emptyLinePlaceholder":72},[49,99,101,104,108,111],{"class":51,"line":100},5,[49,102,103],{"class":55},"def",[49,105,107],{"class":106},"sScJk"," first",[49,109,110],{"class":59},"(items: list[T]) -> T:      ",[49,112,114],{"class":113},"sJ8bj","# output type matches the list's element type\n",[49,116,118,121,124,128],{"class":51,"line":117},6,[49,119,120],{"class":55},"    return",[49,122,123],{"class":59}," items[",[49,125,127],{"class":126},"sj4cs","0",[49,129,130],{"class":59},"]\n",[49,132,134],{"class":51,"line":133},7,[49,135,73],{"emptyLinePlaceholder":72},[49,137,139,142,145,148,151,153,156,159],{"class":51,"line":138},8,[49,140,141],{"class":59},"first([",[49,143,144],{"class":126},"1",[49,146,147],{"class":59},", ",[49,149,150],{"class":126},"2",[49,152,147],{"class":59},[49,154,155],{"class":126},"3",[49,157,158],{"class":59},"])        ",[49,160,161],{"class":113},"# type checker knows this is int\n",[49,163,165,167,170,172,175,178],{"class":51,"line":164},9,[49,166,141],{"class":59},[49,168,169],{"class":88},"\"a\"",[49,171,147],{"class":59},[49,173,174],{"class":88},"\"b\"",[49,176,177],{"class":59},"])       ",[49,179,180],{"class":113},"# ...and this is str\n",[15,182,183,186,187,189],{},[31,184,185],{},"T"," links the parameter and return type, so the checker tracks the concrete type through the\ncall — ",[31,188,33],{}," would have thrown that information away.",[10,191,193],{"id":192},"the-modern-syntax-312","The modern syntax (3.12+)",[15,195,196,197,200],{},"Python 3.12 added clean built-in generic syntax — no explicit ",[31,198,199],{},"TypeVar"," import needed. Both\nstyles are common; you'll see the old one in existing code.",[40,202,204],{"className":42,"code":203,"language":44,"meta":45,"style":45},"# 3.12+\ndef first[T](items: list[T]) -> T:\n    return items[0]\n\n# pre-3.12 (still valid)\nfrom typing import TypeVar\nT = TypeVar(\"T\")\ndef first(items: list[T]) -> T: ...\n",[31,205,206,211,225,235,239,244,254,266],{"__ignoreMap":45},[49,207,208],{"class":51,"line":52},[49,209,210],{"class":113},"# 3.12+\n",[49,212,213,215,218,222],{"class":51,"line":69},[49,214,103],{"class":55},[49,216,217],{"class":59}," first[T](items: list[T]) ",[49,219,221],{"class":220},"s7hpK","->",[49,223,224],{"class":59}," T:\n",[49,226,227,229,231,233],{"class":51,"line":76},[49,228,120],{"class":55},[49,230,123],{"class":59},[49,232,127],{"class":126},[49,234,130],{"class":59},[49,236,237],{"class":51,"line":95},[49,238,73],{"emptyLinePlaceholder":72},[49,240,241],{"class":51,"line":100},[49,242,243],{"class":113},"# pre-3.12 (still valid)\n",[49,245,246,248,250,252],{"class":51,"line":117},[49,247,56],{"class":55},[49,249,60],{"class":59},[49,251,63],{"class":55},[49,253,66],{"class":59},[49,255,256,258,260,262,264],{"class":51,"line":133},[49,257,79],{"class":59},[49,259,82],{"class":55},[49,261,85],{"class":59},[49,263,89],{"class":88},[49,265,92],{"class":59},[49,267,268,270,272,275],{"class":51,"line":138},[49,269,103],{"class":55},[49,271,107],{"class":106},[49,273,274],{"class":59},"(items: list[T]) -> T: ",[49,276,277],{"class":126},"...\n",[15,279,280],{},"The new syntax is more readable and scopes the type parameter to the function or class.",[10,282,284],{"id":283},"generic-classes","Generic classes",[15,286,287,288,291],{},"A class can be generic too, parameterised by one or more type variables. This is how\n",[31,289,290],{},"list[int]"," or a typed container carries its element type.",[40,293,295],{"className":42,"code":294,"language":44,"meta":45,"style":45},"from typing import Generic, TypeVar\n\nT = TypeVar(\"T\")\n\nclass Stack(Generic[T]):\n    def __init__(self) -> None:\n        self._items: list[T] = []\n    def push(self, item: T) -> None:\n        self._items.append(item)\n    def pop(self) -> T:\n        return self._items.pop()\n\ns: Stack[int] = Stack()\ns.push(1)\nreveal_type(s.pop())     # int\n\n# 3.12+: class Stack[T]: ...\n",[31,296,297,308,312,324,328,339,356,369,383,390,401,413,418,435,445,454,459],{"__ignoreMap":45},[49,298,299,301,303,305],{"class":51,"line":52},[49,300,56],{"class":55},[49,302,60],{"class":59},[49,304,63],{"class":55},[49,306,307],{"class":59}," Generic, TypeVar\n",[49,309,310],{"class":51,"line":69},[49,311,73],{"emptyLinePlaceholder":72},[49,313,314,316,318,320,322],{"class":51,"line":76},[49,315,79],{"class":59},[49,317,82],{"class":55},[49,319,85],{"class":59},[49,321,89],{"class":88},[49,323,92],{"class":59},[49,325,326],{"class":51,"line":95},[49,327,73],{"emptyLinePlaceholder":72},[49,329,330,333,336],{"class":51,"line":100},[49,331,332],{"class":55},"class",[49,334,335],{"class":106}," Stack",[49,337,338],{"class":59},"(Generic[T]):\n",[49,340,341,344,347,350,353],{"class":51,"line":117},[49,342,343],{"class":55},"    def",[49,345,346],{"class":126}," __init__",[49,348,349],{"class":59},"(self) -> ",[49,351,352],{"class":126},"None",[49,354,355],{"class":59},":\n",[49,357,358,361,364,366],{"class":51,"line":133},[49,359,360],{"class":126},"        self",[49,362,363],{"class":59},"._items: list[T] ",[49,365,82],{"class":55},[49,367,368],{"class":59}," []\n",[49,370,371,373,376,379,381],{"class":51,"line":138},[49,372,343],{"class":55},[49,374,375],{"class":106}," push",[49,377,378],{"class":59},"(self, item: T) -> ",[49,380,352],{"class":126},[49,382,355],{"class":59},[49,384,385,387],{"class":51,"line":164},[49,386,360],{"class":126},[49,388,389],{"class":59},"._items.append(item)\n",[49,391,393,395,398],{"class":51,"line":392},10,[49,394,343],{"class":55},[49,396,397],{"class":106}," pop",[49,399,400],{"class":59},"(self) -> T:\n",[49,402,404,407,410],{"class":51,"line":403},11,[49,405,406],{"class":55},"        return",[49,408,409],{"class":126}," self",[49,411,412],{"class":59},"._items.pop()\n",[49,414,416],{"class":51,"line":415},12,[49,417,73],{"emptyLinePlaceholder":72},[49,419,421,424,427,430,432],{"class":51,"line":420},13,[49,422,423],{"class":59},"s: Stack[",[49,425,426],{"class":126},"int",[49,428,429],{"class":59},"] ",[49,431,82],{"class":55},[49,433,434],{"class":59}," Stack()\n",[49,436,438,441,443],{"class":51,"line":437},14,[49,439,440],{"class":59},"s.push(",[49,442,144],{"class":126},[49,444,92],{"class":59},[49,446,448,451],{"class":51,"line":447},15,[49,449,450],{"class":59},"reveal_type(s.pop())     ",[49,452,453],{"class":113},"# int\n",[49,455,457],{"class":51,"line":456},16,[49,458,73],{"emptyLinePlaceholder":72},[49,460,462],{"class":51,"line":461},17,[49,463,464],{"class":113},"# 3.12+: class Stack[T]: ...\n",[15,466,467,468,471,472,475,476,479],{},"Now ",[31,469,470],{},"Stack[int]"," and ",[31,473,474],{},"Stack[str]"," are distinct to the type checker, and ",[31,477,478],{},"pop"," returns the\nright element type.",[10,481,483],{"id":482},"bounded-and-constrained-type-variables","Bounded and constrained type variables",[15,485,486,487,490,491,494],{},"You can restrict what a type variable accepts. A ",[19,488,489],{},"bound"," requires a subtype; ",[19,492,493],{},"constraints","\nlimit it to a fixed set of types.",[40,496,498],{"className":42,"code":497,"language":44,"meta":45,"style":45},"from typing import TypeVar\n\n# bound: T must be (a subclass of) a type that supports comparison\nComparable = TypeVar(\"Comparable\", bound=\"SupportsLessThan\")\n\ndef maximum(a: Comparable, b: Comparable) -> Comparable:\n    return a if a > b else b\n\n# constrained: T is exactly int or str, nothing else\nNumeric = TypeVar(\"Numeric\", int, float)\ndef double(x: Numeric) -> Numeric:\n    return x * 2\n",[31,499,500,510,514,519,543,547,557,581,585,590,613,623],{"__ignoreMap":45},[49,501,502,504,506,508],{"class":51,"line":52},[49,503,56],{"class":55},[49,505,60],{"class":59},[49,507,63],{"class":55},[49,509,66],{"class":59},[49,511,512],{"class":51,"line":69},[49,513,73],{"emptyLinePlaceholder":72},[49,515,516],{"class":51,"line":76},[49,517,518],{"class":113},"# bound: T must be (a subclass of) a type that supports comparison\n",[49,520,521,524,526,528,531,533,536,538,541],{"class":51,"line":95},[49,522,523],{"class":59},"Comparable ",[49,525,82],{"class":55},[49,527,85],{"class":59},[49,529,530],{"class":88},"\"Comparable\"",[49,532,147],{"class":59},[49,534,489],{"class":535},"s4XuR",[49,537,82],{"class":55},[49,539,540],{"class":88},"\"SupportsLessThan\"",[49,542,92],{"class":59},[49,544,545],{"class":51,"line":100},[49,546,73],{"emptyLinePlaceholder":72},[49,548,549,551,554],{"class":51,"line":117},[49,550,103],{"class":55},[49,552,553],{"class":106}," maximum",[49,555,556],{"class":59},"(a: Comparable, b: Comparable) -> Comparable:\n",[49,558,559,561,564,567,569,572,575,578],{"class":51,"line":133},[49,560,120],{"class":55},[49,562,563],{"class":59}," a ",[49,565,566],{"class":55},"if",[49,568,563],{"class":59},[49,570,571],{"class":55},">",[49,573,574],{"class":59}," b ",[49,576,577],{"class":55},"else",[49,579,580],{"class":59}," b\n",[49,582,583],{"class":51,"line":138},[49,584,73],{"emptyLinePlaceholder":72},[49,586,587],{"class":51,"line":164},[49,588,589],{"class":113},"# constrained: T is exactly int or str, nothing else\n",[49,591,592,595,597,599,602,604,606,608,611],{"class":51,"line":392},[49,593,594],{"class":59},"Numeric ",[49,596,82],{"class":55},[49,598,85],{"class":59},[49,600,601],{"class":88},"\"Numeric\"",[49,603,147],{"class":59},[49,605,426],{"class":126},[49,607,147],{"class":59},[49,609,610],{"class":126},"float",[49,612,92],{"class":59},[49,614,615,617,620],{"class":51,"line":403},[49,616,103],{"class":55},[49,618,619],{"class":106}," double",[49,621,622],{"class":59},"(x: Numeric) -> Numeric:\n",[49,624,625,627,630,633],{"class":51,"line":415},[49,626,120],{"class":55},[49,628,629],{"class":59}," x ",[49,631,632],{"class":55},"*",[49,634,635],{"class":126}," 2\n",[15,637,638],{},"A bound says \"at least this\"; constraints say \"one of exactly these.\"",[10,640,642],{"id":641},"protocols-structural-typing","Protocols: structural typing",[15,644,645,646,649,650,653],{},"A ",[31,647,648],{},"Protocol"," defines an interface by ",[19,651,652],{},"shape"," — any class with the right methods matches, no\ninheritance required. This is static checking for duck typing.",[40,655,657],{"className":42,"code":656,"language":44,"meta":45,"style":45},"from typing import Protocol\n\nclass Drawable(Protocol):\n    def draw(self) -> str: ...\n\ndef render(obj: Drawable) -> None:\n    print(obj.draw())\n\nclass Circle:                       # does NOT inherit Drawable\n    def draw(self) -> str:\n        return \"○\"\n\nrender(Circle())                    # type-checks — it has draw()\n",[31,658,659,670,674,689,706,710,724,732,736,749,761,768,772],{"__ignoreMap":45},[49,660,661,663,665,667],{"class":51,"line":52},[49,662,56],{"class":55},[49,664,60],{"class":59},[49,666,63],{"class":55},[49,668,669],{"class":59}," Protocol\n",[49,671,672],{"class":51,"line":69},[49,673,73],{"emptyLinePlaceholder":72},[49,675,676,678,681,684,686],{"class":51,"line":76},[49,677,332],{"class":55},[49,679,680],{"class":106}," Drawable",[49,682,683],{"class":59},"(",[49,685,648],{"class":106},[49,687,688],{"class":59},"):\n",[49,690,691,693,696,698,701,704],{"class":51,"line":95},[49,692,343],{"class":55},[49,694,695],{"class":106}," draw",[49,697,349],{"class":59},[49,699,700],{"class":126},"str",[49,702,703],{"class":59},": ",[49,705,277],{"class":126},[49,707,708],{"class":51,"line":100},[49,709,73],{"emptyLinePlaceholder":72},[49,711,712,714,717,720,722],{"class":51,"line":117},[49,713,103],{"class":55},[49,715,716],{"class":106}," render",[49,718,719],{"class":59},"(obj: Drawable) -> ",[49,721,352],{"class":126},[49,723,355],{"class":59},[49,725,726,729],{"class":51,"line":133},[49,727,728],{"class":126},"    print",[49,730,731],{"class":59},"(obj.draw())\n",[49,733,734],{"class":51,"line":138},[49,735,73],{"emptyLinePlaceholder":72},[49,737,738,740,743,746],{"class":51,"line":164},[49,739,332],{"class":55},[49,741,742],{"class":106}," Circle",[49,744,745],{"class":59},":                       ",[49,747,748],{"class":113},"# does NOT inherit Drawable\n",[49,750,751,753,755,757,759],{"class":51,"line":392},[49,752,343],{"class":55},[49,754,695],{"class":106},[49,756,349],{"class":59},[49,758,700],{"class":126},[49,760,355],{"class":59},[49,762,763,765],{"class":51,"line":403},[49,764,406],{"class":55},[49,766,767],{"class":88}," \"○\"\n",[49,769,770],{"class":51,"line":415},[49,771,73],{"emptyLinePlaceholder":72},[49,773,774,777],{"class":51,"line":420},[49,775,776],{"class":59},"render(Circle())                    ",[49,778,779],{"class":113},"# type-checks — it has draw()\n",[15,781,782,785,786,789,790,793],{},[31,783,784],{},"Circle"," satisfies ",[31,787,788],{},"Drawable"," simply by having a compatible ",[31,791,792],{},"draw"," method. This is far more\nPythonic than forcing an explicit base class.",[10,795,797],{"id":796},"runtime_checkable-and-where-protocols-shine","runtime_checkable and where protocols shine",[15,799,800,801,804,805,808],{},"Decorate a Protocol with ",[31,802,803],{},"@runtime_checkable"," to allow ",[31,806,807],{},"isinstance"," checks against it (it\nchecks method presence, not signatures). Protocols are ideal for typing the \"anything with\nthese methods\" parameters that pervade Python.",[40,810,812],{"className":42,"code":811,"language":44,"meta":45,"style":45},"from typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass SupportsClose(Protocol):\n    def close(self) -> None: ...\n\nisinstance(open(\"f\"), SupportsClose)   # True — files have close()\n",[31,813,814,825,829,834,847,862,866],{"__ignoreMap":45},[49,815,816,818,820,822],{"class":51,"line":52},[49,817,56],{"class":55},[49,819,60],{"class":59},[49,821,63],{"class":55},[49,823,824],{"class":59}," Protocol, runtime_checkable\n",[49,826,827],{"class":51,"line":69},[49,828,73],{"emptyLinePlaceholder":72},[49,830,831],{"class":51,"line":76},[49,832,833],{"class":106},"@runtime_checkable\n",[49,835,836,838,841,843,845],{"class":51,"line":95},[49,837,332],{"class":55},[49,839,840],{"class":106}," SupportsClose",[49,842,683],{"class":59},[49,844,648],{"class":106},[49,846,688],{"class":59},[49,848,849,851,854,856,858,860],{"class":51,"line":100},[49,850,343],{"class":55},[49,852,853],{"class":106}," close",[49,855,349],{"class":59},[49,857,352],{"class":126},[49,859,703],{"class":59},[49,861,277],{"class":126},[49,863,864],{"class":51,"line":117},[49,865,73],{"emptyLinePlaceholder":72},[49,867,868,870,872,875,877,880,883],{"class":51,"line":133},[49,869,807],{"class":126},[49,871,683],{"class":59},[49,873,874],{"class":126},"open",[49,876,683],{"class":59},[49,878,879],{"class":88},"\"f\"",[49,881,882],{"class":59},"), SupportsClose)   ",[49,884,885],{"class":113},"# True — files have close()\n",[15,887,888],{},"Use Protocols for dependency interfaces, plugin contracts, and standard \"supports X\" shapes\nlike iterables and context managers.",[10,890,892],{"id":891},"recap","Recap",[15,894,895,898,899,903,904,907,908,911,912,915,916,919,920,922,923,925,926,928,929,932,933,935,936,938,939,941],{},[19,896,897],{},"Generics"," keep type information flowing through reusable code: a ",[19,900,901],{},[31,902,199],{}," (or the\n3.12 ",[31,905,906],{},"def f[T]"," \u002F ",[31,909,910],{},"class C[T]"," syntax) links inputs to outputs, and ",[19,913,914],{},"generic classes","\n(",[31,917,918],{},"Generic[T]",") carry an element type like ",[31,921,470],{},". Restrict type variables with a\n",[19,924,489],{}," (\"at least this\") or ",[19,927,493],{}," (\"one of these\"). ",[19,930,931],{},"Protocols"," bring static\nchecking to duck typing — a class matches by ",[19,934,652],{},", not inheritance — and\n",[31,937,803],{}," enables ",[31,940,807],{}," checks. Together they let you type flexible Python\nprecisely without sacrificing its dynamism.",[943,944,945],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":45,"searchDepth":69,"depth":69,"links":947},[948,949,950,951,952,953,954,955],{"id":12,"depth":69,"text":13},{"id":25,"depth":69,"text":26},{"id":192,"depth":69,"text":193},{"id":283,"depth":69,"text":284},{"id":482,"depth":69,"text":483},{"id":641,"depth":69,"text":642},{"id":796,"depth":69,"text":797},{"id":891,"depth":69,"text":892},"How to write reusable, precisely-typed Python — generics with TypeVar and the 3.12 syntax, generic classes, bounded type variables, and Protocols for structural (duck) typing.","hard","md","Python",{},"\u002Fblog\u002Fpython-generics-protocols-explained","\u002Fpython\u002Ftyping\u002Fgenerics-protocols",{"title":5,"description":956},"blog\u002Fpython-generics-protocols-explained","Generics & Protocols","Type Hints & Typing","typing","2026-06-19","XWNYrqD1yUWxeIsAsrkGuCjrkNw4F0GqRs1ukOz_244",1782244092880]