[{"data":1,"prerenderedAt":1478},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-abc-vs-protocol":3},{"id":4,"title":5,"body":6,"description":1465,"difficulty":1466,"extension":1467,"framework":1468,"frameworkSlug":269,"meta":1469,"navigation":301,"order":362,"path":1470,"qaPath":654,"seo":1471,"stem":1472,"subtopic":1473,"topic":1474,"topicSlug":1475,"updated":1476,"__hash__":1477},"blog\u002Fblog\u002Fpython-abc-vs-protocol.md","Python ABC vs Protocol — Nominal vs Structural Typing Explained",{"type":7,"value":8,"toc":1454},"minimark",[9,14,38,42,56,66,70,253,257,264,596,638,645,656,660,677,873,890,969,980,983,1004,1012,1016,1026,1186,1189,1193,1200,1343,1347,1421,1425,1450],[10,11,13],"h2",{"id":12},"two-ways-to-define-an-interface-in-python","Two ways to define an interface in Python",[15,16,17,18,22,23,29,30,33,34,37],"p",{},"Python is famous for duck typing — \"if it walks like a duck and quacks like a duck, it's\na duck.\" But modern Python gives you two formal tools for defining interfaces on top of\nthat: ",[19,20,21],"strong",{},"Abstract Base Classes (ABCs)"," and ",[19,24,25],{},[26,27,28],"code",{},"typing.Protocol",". They look similar at a\nglance but rest on opposite philosophies — ",[19,31,32],{},"nominal"," subtyping vs ",[19,35,36],{},"structural","\nsubtyping — and that difference drives every practical choice between them.",[10,39,41],{"id":40},"the-core-distinction","The core distinction",[15,43,44,47,48,51,52,55],{},[19,45,46],{},"ABCs use nominal (explicit) subtyping:"," a class satisfies the interface only if it\nexplicitly declares ",[26,49,50],{},"class MyClass(MyABC)",". Python enforces this at ",[19,53,54],{},"runtime"," by\nrefusing to instantiate classes that miss abstract methods.",[15,57,58,61,62,65],{},[19,59,60],{},"Protocols use structural (implicit) subtyping:"," a class satisfies the interface if it\nsimply has the right methods and attributes — no inheritance declaration required. Type\ncheckers like mypy enforce this ",[19,63,64],{},"statically",", at lint\u002Fcheck time.",[10,67,69],{"id":68},"quick-reference-comparison","Quick-reference comparison",[71,72,73,95],"table",{},[74,75,76],"thead",{},[77,78,79,82,91],"tr",{},[80,81],"th",{},[80,83,84,87,88],{},[26,85,86],{},"abc.ABC"," + ",[26,89,90],{},"@abstractmethod",[80,92,93],{},[26,94,28],{},[96,97,98,115,132,148,161,177,190,206,221,234],"tbody",{},[77,99,100,106,112],{},[101,102,103],"td",{},[19,104,105],{},"Subtyping style",[101,107,108,109],{},"Nominal — must ",[26,110,111],{},"class Foo(MyABC)",[101,113,114],{},"Structural — matching methods is enough",[77,116,117,122,129],{},[101,118,119],{},[19,120,121],{},"Explicit registration?",[101,123,124,125,128],{},"Yes (inherit or ",[26,126,127],{},"register()",")",[101,130,131],{},"No — implicit via shape",[77,133,134,139,142],{},[101,135,136],{},[19,137,138],{},"Runtime enforcement?",[101,140,141],{},"Yes — TypeError on missing methods",[101,143,144,145],{},"Only with ",[26,146,147],{},"@runtime_checkable",[77,149,150,155,158],{},[101,151,152],{},[19,153,154],{},"Static type checking?",[101,156,157],{},"Yes",[101,159,160],{},"Yes (primary use case)",[77,162,163,171,173],{},[101,164,165],{},[19,166,167,170],{},[26,168,169],{},"isinstance()"," works?",[101,172,157],{},[101,174,144,175],{},[26,176,147],{},[77,178,179,184,187],{},[101,180,181],{},[19,182,183],{},"Shared implementation?",[101,185,186],{},"Yes — concrete mixin methods",[101,188,189],{},"No — Protocols are pure interface",[77,191,192,197,203],{},[101,193,194],{},[19,195,196],{},"Third-party classes?",[101,198,199,200,202],{},"Via ",[26,201,127],{}," hack",[101,204,205],{},"Yes — if they have the right shape",[77,207,208,216,218],{},[101,209,210],{},[19,211,212,215],{},[26,213,214],{},"collections.abc"," integration?",[101,217,157],{},[101,219,220],{},"Separate",[77,222,223,228,231],{},[101,224,225],{},[19,226,227],{},"Best for",[101,229,230],{},"Families of related classes you control",[101,232,233],{},"Defining callable\u002Fstructural contracts, typing third-party code",[77,235,236,241,248],{},[101,237,238],{},[19,239,240],{},"Added in",[101,242,243,244,247],{},"Python 2.6+ (",[26,245,246],{},"abc"," module)",[101,249,250,251,128],{},"Python 3.8+ (",[26,252,28],{},[10,254,256],{"id":255},"abcs-explicit-contracts-with-runtime-enforcement","ABCs — explicit contracts with runtime enforcement",[15,258,259,260,263],{},"An ABC requires that every concrete subclass explicitly inherits from it and implements all\nabstract methods. The enforcement happens at ",[19,261,262],{},"instantiation time"," — not at definition\ntime — which catches missing methods early.",[265,266,271],"pre",{"className":267,"code":268,"language":269,"meta":270,"style":270},"language-python shiki shiki-themes github-light github-dark","from abc import ABC, abstractmethod\n\nclass Serializer(ABC):\n    @abstractmethod\n    def serialize(self, data: dict) -> str: ...\n\n    @abstractmethod\n    def deserialize(self, text: str) -> dict: ...\n\n    def round_trip(self, data: dict) -> dict:    # concrete mixin method\n        return self.deserialize(self.serialize(data))\n\nclass JsonSerializer(Serializer):\n    def serialize(self, data):\n        import json; return json.dumps(data)\n\n    def deserialize(self, text):\n        import json; return json.loads(text)\n\nSerializer()         # TypeError — can't instantiate abstract class\nJsonSerializer()     # OK — all abstract methods implemented\n\n# Works even without explicit subclassing if you use register():\nclass ThirdPartySerializer:\n    def serialize(self, data): ...\n    def deserialize(self, text): ...\n\nSerializer.register(ThirdPartySerializer)\nisinstance(ThirdPartySerializer(), Serializer)   # True — registered\n","python","",[26,272,273,296,303,322,328,355,360,365,386,391,414,432,437,452,462,477,482,492,504,509,518,527,532,538,549,561,573,578,584],{"__ignoreMap":270},[274,275,278,282,286,289,293],"span",{"class":276,"line":277},"line",1,[274,279,281],{"class":280},"szBVR","from",[274,283,285],{"class":284},"sVt8B"," abc ",[274,287,288],{"class":280},"import",[274,290,292],{"class":291},"sj4cs"," ABC",[274,294,295],{"class":284},", abstractmethod\n",[274,297,299],{"class":276,"line":298},2,[274,300,302],{"emptyLinePlaceholder":301},true,"\n",[274,304,306,309,313,316,319],{"class":276,"line":305},3,[274,307,308],{"class":280},"class",[274,310,312],{"class":311},"sScJk"," Serializer",[274,314,315],{"class":284},"(",[274,317,318],{"class":291},"ABC",[274,320,321],{"class":284},"):\n",[274,323,325],{"class":276,"line":324},4,[274,326,327],{"class":311},"    @abstractmethod\n",[274,329,331,334,337,340,343,346,349,352],{"class":276,"line":330},5,[274,332,333],{"class":280},"    def",[274,335,336],{"class":311}," serialize",[274,338,339],{"class":284},"(self, data: ",[274,341,342],{"class":291},"dict",[274,344,345],{"class":284},") -> ",[274,347,348],{"class":291},"str",[274,350,351],{"class":284},": ",[274,353,354],{"class":291},"...\n",[274,356,358],{"class":276,"line":357},6,[274,359,302],{"emptyLinePlaceholder":301},[274,361,363],{"class":276,"line":362},7,[274,364,327],{"class":311},[274,366,368,370,373,376,378,380,382,384],{"class":276,"line":367},8,[274,369,333],{"class":280},[274,371,372],{"class":311}," deserialize",[274,374,375],{"class":284},"(self, text: ",[274,377,348],{"class":291},[274,379,345],{"class":284},[274,381,342],{"class":291},[274,383,351],{"class":284},[274,385,354],{"class":291},[274,387,389],{"class":276,"line":388},9,[274,390,302],{"emptyLinePlaceholder":301},[274,392,394,396,399,401,403,405,407,410],{"class":276,"line":393},10,[274,395,333],{"class":280},[274,397,398],{"class":311}," round_trip",[274,400,339],{"class":284},[274,402,342],{"class":291},[274,404,345],{"class":284},[274,406,342],{"class":291},[274,408,409],{"class":284},":    ",[274,411,413],{"class":412},"sJ8bj","# concrete mixin method\n",[274,415,417,420,423,426,429],{"class":276,"line":416},11,[274,418,419],{"class":280},"        return",[274,421,422],{"class":291}," self",[274,424,425],{"class":284},".deserialize(",[274,427,428],{"class":291},"self",[274,430,431],{"class":284},".serialize(data))\n",[274,433,435],{"class":276,"line":434},12,[274,436,302],{"emptyLinePlaceholder":301},[274,438,440,442,445,447,450],{"class":276,"line":439},13,[274,441,308],{"class":280},[274,443,444],{"class":311}," JsonSerializer",[274,446,315],{"class":284},[274,448,449],{"class":311},"Serializer",[274,451,321],{"class":284},[274,453,455,457,459],{"class":276,"line":454},14,[274,456,333],{"class":280},[274,458,336],{"class":311},[274,460,461],{"class":284},"(self, data):\n",[274,463,465,468,471,474],{"class":276,"line":464},15,[274,466,467],{"class":280},"        import",[274,469,470],{"class":284}," json; ",[274,472,473],{"class":280},"return",[274,475,476],{"class":284}," json.dumps(data)\n",[274,478,480],{"class":276,"line":479},16,[274,481,302],{"emptyLinePlaceholder":301},[274,483,485,487,489],{"class":276,"line":484},17,[274,486,333],{"class":280},[274,488,372],{"class":311},[274,490,491],{"class":284},"(self, text):\n",[274,493,495,497,499,501],{"class":276,"line":494},18,[274,496,467],{"class":280},[274,498,470],{"class":284},[274,500,473],{"class":280},[274,502,503],{"class":284}," json.loads(text)\n",[274,505,507],{"class":276,"line":506},19,[274,508,302],{"emptyLinePlaceholder":301},[274,510,512,515],{"class":276,"line":511},20,[274,513,514],{"class":284},"Serializer()         ",[274,516,517],{"class":412},"# TypeError — can't instantiate abstract class\n",[274,519,521,524],{"class":276,"line":520},21,[274,522,523],{"class":284},"JsonSerializer()     ",[274,525,526],{"class":412},"# OK — all abstract methods implemented\n",[274,528,530],{"class":276,"line":529},22,[274,531,302],{"emptyLinePlaceholder":301},[274,533,535],{"class":276,"line":534},23,[274,536,537],{"class":412},"# Works even without explicit subclassing if you use register():\n",[274,539,541,543,546],{"class":276,"line":540},24,[274,542,308],{"class":280},[274,544,545],{"class":311}," ThirdPartySerializer",[274,547,548],{"class":284},":\n",[274,550,552,554,556,559],{"class":276,"line":551},25,[274,553,333],{"class":280},[274,555,336],{"class":311},[274,557,558],{"class":284},"(self, data): ",[274,560,354],{"class":291},[274,562,564,566,568,571],{"class":276,"line":563},26,[274,565,333],{"class":280},[274,567,372],{"class":311},[274,569,570],{"class":284},"(self, text): ",[274,572,354],{"class":291},[274,574,576],{"class":276,"line":575},27,[274,577,302],{"emptyLinePlaceholder":301},[274,579,581],{"class":276,"line":580},28,[274,582,583],{"class":284},"Serializer.register(ThirdPartySerializer)\n",[274,585,587,590,593],{"class":276,"line":586},29,[274,588,589],{"class":291},"isinstance",[274,591,592],{"class":284},"(ThirdPartySerializer(), Serializer)   ",[274,594,595],{"class":412},"# True — registered\n",[15,597,598,600,601,604,605,604,608,604,611,614,615,617,618,22,621,624,625,604,628,604,631,604,634,637],{},[26,599,214],{}," provides battle-tested ABCs for standard container protocols:\n",[26,602,603],{},"Iterable",", ",[26,606,607],{},"Sequence",[26,609,610],{},"Mapping",[26,612,613],{},"Callable",", and many more. Subclassing these gives you\nfree mixin implementations (e.g. subclass ",[26,616,607],{}," with just ",[26,619,620],{},"__getitem__",[26,622,623],{},"__len__","\nand get ",[26,626,627],{},"__contains__",[26,629,630],{},"__iter__",[26,632,633],{},"index",[26,635,636],{},"count"," for free).",[15,639,640,641,644],{},"Use ABCs when you have a ",[19,642,643],{},"tight family of classes that must share a contract and you\ncontrol the class hierarchy"," — and when you want clear runtime errors at instantiation.",[646,647,648],"blockquote",{},[15,649,650,651],{},"Deep dive: ",[652,653,655],"a",{"href":654},"\u002Fpython\u002Foop\u002Fabc-protocols","Abstract Base Classes & Protocols interview questions",[10,657,659],{"id":658},"protocols-structural-typing-for-duck-typing-with-type-safety","Protocols — structural typing for duck typing with type safety",[15,661,662,664,665,668,669,672,673,676],{},[26,663,28],{}," lets you define an interface that any class satisfies ",[19,666,667],{},"simply by having\nthe right methods and attributes"," — no inheritance required. This is how duck typing\nworks at the type-checker level: a class \"is a\" ",[26,670,671],{},"Drawable"," if it has a ",[26,674,675],{},"draw()"," method,\nfull stop.",[265,678,680],{"className":267,"code":679,"language":269,"meta":270,"style":270},"from typing import Protocol\n\nclass Drawable(Protocol):\n    def draw(self) -> None: ...\n\nclass Circle:                          # no inheritance from Drawable\n    def draw(self) -> None:\n        print(\"drawing circle\")\n\nclass Square:                          # also has draw() — also satisfies Drawable\n    def draw(self) -> None:\n        print(\"drawing square\")\n\ndef render(shape: Drawable) -> None:   # type checker accepts any matching type\n    shape.draw()\n\nrender(Circle())    # OK — Circle has draw()\nrender(Square())    # OK — Square has draw()\nrender(\"hello\")     # mypy error — str has no draw()\n",[26,681,682,694,698,712,729,733,746,758,772,776,788,800,811,815,834,839,843,851,859],{"__ignoreMap":270},[274,683,684,686,689,691],{"class":276,"line":277},[274,685,281],{"class":280},[274,687,688],{"class":284}," typing ",[274,690,288],{"class":280},[274,692,693],{"class":284}," Protocol\n",[274,695,696],{"class":276,"line":298},[274,697,302],{"emptyLinePlaceholder":301},[274,699,700,702,705,707,710],{"class":276,"line":305},[274,701,308],{"class":280},[274,703,704],{"class":311}," Drawable",[274,706,315],{"class":284},[274,708,709],{"class":311},"Protocol",[274,711,321],{"class":284},[274,713,714,716,719,722,725,727],{"class":276,"line":324},[274,715,333],{"class":280},[274,717,718],{"class":311}," draw",[274,720,721],{"class":284},"(self) -> ",[274,723,724],{"class":291},"None",[274,726,351],{"class":284},[274,728,354],{"class":291},[274,730,731],{"class":276,"line":330},[274,732,302],{"emptyLinePlaceholder":301},[274,734,735,737,740,743],{"class":276,"line":357},[274,736,308],{"class":280},[274,738,739],{"class":311}," Circle",[274,741,742],{"class":284},":                          ",[274,744,745],{"class":412},"# no inheritance from Drawable\n",[274,747,748,750,752,754,756],{"class":276,"line":362},[274,749,333],{"class":280},[274,751,718],{"class":311},[274,753,721],{"class":284},[274,755,724],{"class":291},[274,757,548],{"class":284},[274,759,760,763,765,769],{"class":276,"line":367},[274,761,762],{"class":291},"        print",[274,764,315],{"class":284},[274,766,768],{"class":767},"sZZnC","\"drawing circle\"",[274,770,771],{"class":284},")\n",[274,773,774],{"class":276,"line":388},[274,775,302],{"emptyLinePlaceholder":301},[274,777,778,780,783,785],{"class":276,"line":393},[274,779,308],{"class":280},[274,781,782],{"class":311}," Square",[274,784,742],{"class":284},[274,786,787],{"class":412},"# also has draw() — also satisfies Drawable\n",[274,789,790,792,794,796,798],{"class":276,"line":416},[274,791,333],{"class":280},[274,793,718],{"class":311},[274,795,721],{"class":284},[274,797,724],{"class":291},[274,799,548],{"class":284},[274,801,802,804,806,809],{"class":276,"line":434},[274,803,762],{"class":291},[274,805,315],{"class":284},[274,807,808],{"class":767},"\"drawing square\"",[274,810,771],{"class":284},[274,812,813],{"class":276,"line":439},[274,814,302],{"emptyLinePlaceholder":301},[274,816,817,820,823,826,828,831],{"class":276,"line":454},[274,818,819],{"class":280},"def",[274,821,822],{"class":311}," render",[274,824,825],{"class":284},"(shape: Drawable) -> ",[274,827,724],{"class":291},[274,829,830],{"class":284},":   ",[274,832,833],{"class":412},"# type checker accepts any matching type\n",[274,835,836],{"class":276,"line":464},[274,837,838],{"class":284},"    shape.draw()\n",[274,840,841],{"class":276,"line":479},[274,842,302],{"emptyLinePlaceholder":301},[274,844,845,848],{"class":276,"line":484},[274,846,847],{"class":284},"render(Circle())    ",[274,849,850],{"class":412},"# OK — Circle has draw()\n",[274,852,853,856],{"class":276,"line":494},[274,854,855],{"class":284},"render(Square())    ",[274,857,858],{"class":412},"# OK — Square has draw()\n",[274,860,861,864,867,870],{"class":276,"line":506},[274,862,863],{"class":284},"render(",[274,865,866],{"class":767},"\"hello\"",[274,868,869],{"class":284},")     ",[274,871,872],{"class":412},"# mypy error — str has no draw()\n",[15,874,875,876,879,880,883,884,886,887,889],{},"Neither ",[26,877,878],{},"Circle"," nor ",[26,881,882],{},"Square"," inherits from ",[26,885,671],{},". The type checker accepts them\npurely because their shape matches. At runtime there's no check unless you add\n",[26,888,147],{},":",[265,891,893],{"className":267,"code":892,"language":269,"meta":270,"style":270},"from typing import Protocol, runtime_checkable\n\n@runtime_checkable\nclass Drawable(Protocol):\n    def draw(self) -> None: ...\n\nisinstance(Circle(), Drawable)   # True — runtime shape check\nisinstance(\"hello\", Drawable)    # False\n",[26,894,895,906,910,915,927,941,945,955],{"__ignoreMap":270},[274,896,897,899,901,903],{"class":276,"line":277},[274,898,281],{"class":280},[274,900,688],{"class":284},[274,902,288],{"class":280},[274,904,905],{"class":284}," Protocol, runtime_checkable\n",[274,907,908],{"class":276,"line":298},[274,909,302],{"emptyLinePlaceholder":301},[274,911,912],{"class":276,"line":305},[274,913,914],{"class":311},"@runtime_checkable\n",[274,916,917,919,921,923,925],{"class":276,"line":324},[274,918,308],{"class":280},[274,920,704],{"class":311},[274,922,315],{"class":284},[274,924,709],{"class":311},[274,926,321],{"class":284},[274,928,929,931,933,935,937,939],{"class":276,"line":330},[274,930,333],{"class":280},[274,932,718],{"class":311},[274,934,721],{"class":284},[274,936,724],{"class":291},[274,938,351],{"class":284},[274,940,354],{"class":291},[274,942,943],{"class":276,"line":357},[274,944,302],{"emptyLinePlaceholder":301},[274,946,947,949,952],{"class":276,"line":362},[274,948,589],{"class":291},[274,950,951],{"class":284},"(Circle(), Drawable)   ",[274,953,954],{"class":412},"# True — runtime shape check\n",[274,956,957,959,961,963,966],{"class":276,"line":367},[274,958,589],{"class":291},[274,960,315],{"class":284},[274,962,866],{"class":767},[274,964,965],{"class":284},", Drawable)    ",[274,967,968],{"class":412},"# False\n",[15,970,971,972,974,975,979],{},"Note: ",[26,973,147],{}," only checks for method\u002Fattribute ",[976,977,978],"em",{},"presence",", not signatures.",[15,981,982],{},"Use Protocols when:",[984,985,986,994,997],"ul",{},[987,988,989,990,993],"li",{},"You're writing a function that accepts ",[19,991,992],{},"any class with a given shape",", including\nthird-party classes you can't modify.",[987,995,996],{},"You want duck-typed flexibility with static type safety.",[987,998,999,1000,1003],{},"The relationship is a ",[19,1001,1002],{},"capability"," (\"can be drawn\") rather than a family (\"is a Shape\").",[646,1005,1006],{},[15,1007,650,1008],{},[652,1009,1011],{"href":1010},"\u002Fpython\u002Ftyping\u002Fgenerics-protocols","Generics & Protocols interview questions",[10,1013,1015],{"id":1014},"the-key-practical-difference-third-party-classes","The key practical difference — third-party classes",[15,1017,1018,1019,1022,1023,1025],{},"The most important real-world difference: ",[19,1020,1021],{},"you can't make a third-party class inherit\nfrom your ABC"," (without ",[26,1024,127],{},", which is a workaround). But any third-party class\nthat happens to have the right methods automatically satisfies a Protocol.",[265,1027,1029],{"className":267,"code":1028,"language":269,"meta":270,"style":270},"import pandas as pd\n\n# ABC: pandas DataFrame does NOT subclass your ABC — won't work without register()\nclass Tabular(ABC):\n    @abstractmethod\n    def to_csv(self) -> str: ...\n\nisinstance(pd.DataFrame(), Tabular)   # False — unless registered manually\n\n# Protocol: DataFrame has to_csv() — it automatically satisfies the Protocol\nfrom typing import Protocol\n\nclass Tabular(Protocol):\n    def to_csv(self) -> str: ...\n\ndef export(data: Tabular) -> None:\n    print(data.to_csv())\n\nexport(pd.DataFrame())    # type checker: OK  (DataFrame has to_csv)\n",[26,1030,1031,1044,1048,1053,1066,1070,1085,1089,1099,1103,1108,1118,1122,1134,1148,1152,1166,1174,1178],{"__ignoreMap":270},[274,1032,1033,1035,1038,1041],{"class":276,"line":277},[274,1034,288],{"class":280},[274,1036,1037],{"class":284}," pandas ",[274,1039,1040],{"class":280},"as",[274,1042,1043],{"class":284}," pd\n",[274,1045,1046],{"class":276,"line":298},[274,1047,302],{"emptyLinePlaceholder":301},[274,1049,1050],{"class":276,"line":305},[274,1051,1052],{"class":412},"# ABC: pandas DataFrame does NOT subclass your ABC — won't work without register()\n",[274,1054,1055,1057,1060,1062,1064],{"class":276,"line":324},[274,1056,308],{"class":280},[274,1058,1059],{"class":311}," Tabular",[274,1061,315],{"class":284},[274,1063,318],{"class":291},[274,1065,321],{"class":284},[274,1067,1068],{"class":276,"line":330},[274,1069,327],{"class":311},[274,1071,1072,1074,1077,1079,1081,1083],{"class":276,"line":357},[274,1073,333],{"class":280},[274,1075,1076],{"class":311}," to_csv",[274,1078,721],{"class":284},[274,1080,348],{"class":291},[274,1082,351],{"class":284},[274,1084,354],{"class":291},[274,1086,1087],{"class":276,"line":362},[274,1088,302],{"emptyLinePlaceholder":301},[274,1090,1091,1093,1096],{"class":276,"line":367},[274,1092,589],{"class":291},[274,1094,1095],{"class":284},"(pd.DataFrame(), Tabular)   ",[274,1097,1098],{"class":412},"# False — unless registered manually\n",[274,1100,1101],{"class":276,"line":388},[274,1102,302],{"emptyLinePlaceholder":301},[274,1104,1105],{"class":276,"line":393},[274,1106,1107],{"class":412},"# Protocol: DataFrame has to_csv() — it automatically satisfies the Protocol\n",[274,1109,1110,1112,1114,1116],{"class":276,"line":416},[274,1111,281],{"class":280},[274,1113,688],{"class":284},[274,1115,288],{"class":280},[274,1117,693],{"class":284},[274,1119,1120],{"class":276,"line":434},[274,1121,302],{"emptyLinePlaceholder":301},[274,1123,1124,1126,1128,1130,1132],{"class":276,"line":439},[274,1125,308],{"class":280},[274,1127,1059],{"class":311},[274,1129,315],{"class":284},[274,1131,709],{"class":311},[274,1133,321],{"class":284},[274,1135,1136,1138,1140,1142,1144,1146],{"class":276,"line":454},[274,1137,333],{"class":280},[274,1139,1076],{"class":311},[274,1141,721],{"class":284},[274,1143,348],{"class":291},[274,1145,351],{"class":284},[274,1147,354],{"class":291},[274,1149,1150],{"class":276,"line":464},[274,1151,302],{"emptyLinePlaceholder":301},[274,1153,1154,1156,1159,1162,1164],{"class":276,"line":479},[274,1155,819],{"class":280},[274,1157,1158],{"class":311}," export",[274,1160,1161],{"class":284},"(data: Tabular) -> ",[274,1163,724],{"class":291},[274,1165,548],{"class":284},[274,1167,1168,1171],{"class":276,"line":484},[274,1169,1170],{"class":291},"    print",[274,1172,1173],{"class":284},"(data.to_csv())\n",[274,1175,1176],{"class":276,"line":494},[274,1177,302],{"emptyLinePlaceholder":301},[274,1179,1180,1183],{"class":276,"line":506},[274,1181,1182],{"class":284},"export(pd.DataFrame())    ",[274,1184,1185],{"class":412},"# type checker: OK  (DataFrame has to_csv)\n",[15,1187,1188],{},"This is why modern Python typing favours Protocols for library code and public APIs.",[10,1190,1192],{"id":1191},"when-to-share-implementation-abcs-win","When to share implementation — ABCs win",[15,1194,1195,1196,1199],{},"Protocols are ",[19,1197,1198],{},"pure interface"," — no concrete methods. If you need shared implementation\nalongside the contract, use an ABC:",[265,1201,1203],{"className":267,"code":1202,"language":269,"meta":270,"style":270},"class Animal(ABC):\n    @abstractmethod\n    def speak(self) -> str: ...\n\n    def describe(self) -> str:         # concrete mixin — shared by all subclasses\n        return f\"I am a {type(self).__name__} and I say {self.speak()}\"\n\nclass Dog(Animal):\n    def speak(self) -> str: return \"woof\"\n\nDog().describe()   # \"I am a Dog and I say woof\"\n",[26,1204,1205,1218,1222,1237,1241,1258,1296,1300,1314,1331,1335],{"__ignoreMap":270},[274,1206,1207,1209,1212,1214,1216],{"class":276,"line":277},[274,1208,308],{"class":280},[274,1210,1211],{"class":311}," Animal",[274,1213,315],{"class":284},[274,1215,318],{"class":291},[274,1217,321],{"class":284},[274,1219,1220],{"class":276,"line":298},[274,1221,327],{"class":311},[274,1223,1224,1226,1229,1231,1233,1235],{"class":276,"line":305},[274,1225,333],{"class":280},[274,1227,1228],{"class":311}," speak",[274,1230,721],{"class":284},[274,1232,348],{"class":291},[274,1234,351],{"class":284},[274,1236,354],{"class":291},[274,1238,1239],{"class":276,"line":324},[274,1240,302],{"emptyLinePlaceholder":301},[274,1242,1243,1245,1248,1250,1252,1255],{"class":276,"line":330},[274,1244,333],{"class":280},[274,1246,1247],{"class":311}," describe",[274,1249,721],{"class":284},[274,1251,348],{"class":291},[274,1253,1254],{"class":284},":         ",[274,1256,1257],{"class":412},"# concrete mixin — shared by all subclasses\n",[274,1259,1260,1262,1265,1268,1271,1273,1275,1278,1281,1284,1287,1290,1293],{"class":276,"line":357},[274,1261,419],{"class":280},[274,1263,1264],{"class":280}," f",[274,1266,1267],{"class":767},"\"I am a ",[274,1269,1270],{"class":291},"{type",[274,1272,315],{"class":284},[274,1274,428],{"class":291},[274,1276,1277],{"class":284},").",[274,1279,1280],{"class":291},"__name__}",[274,1282,1283],{"class":767}," and I say ",[274,1285,1286],{"class":291},"{self",[274,1288,1289],{"class":284},".speak()",[274,1291,1292],{"class":291},"}",[274,1294,1295],{"class":767},"\"\n",[274,1297,1298],{"class":276,"line":362},[274,1299,302],{"emptyLinePlaceholder":301},[274,1301,1302,1304,1307,1309,1312],{"class":276,"line":367},[274,1303,308],{"class":280},[274,1305,1306],{"class":311}," Dog",[274,1308,315],{"class":284},[274,1310,1311],{"class":311},"Animal",[274,1313,321],{"class":284},[274,1315,1316,1318,1320,1322,1324,1326,1328],{"class":276,"line":388},[274,1317,333],{"class":280},[274,1319,1228],{"class":311},[274,1321,721],{"class":284},[274,1323,348],{"class":291},[274,1325,351],{"class":284},[274,1327,473],{"class":280},[274,1329,1330],{"class":767}," \"woof\"\n",[274,1332,1333],{"class":276,"line":393},[274,1334,302],{"emptyLinePlaceholder":301},[274,1336,1337,1340],{"class":276,"line":416},[274,1338,1339],{"class":284},"Dog().describe()   ",[274,1341,1342],{"class":412},"# \"I am a Dog and I say woof\"\n",[10,1344,1346],{"id":1345},"the-decision-rule","The decision rule",[71,1348,1349,1359],{},[74,1350,1351],{},[77,1352,1353,1356],{},[80,1354,1355],{},"Situation",[80,1357,1358],{},"Choose",[96,1360,1361,1370,1379,1388,1397,1412],{},[77,1362,1363,1366],{},[101,1364,1365],{},"Family of related classes you control, need runtime enforcement",[101,1367,1368],{},[26,1369,318],{},[77,1371,1372,1375],{},[101,1373,1374],{},"Need shared (mixin) implementation alongside the contract",[101,1376,1377],{},[26,1378,318],{},[77,1380,1381,1384],{},[101,1382,1383],{},"Want to type-hint a structural capability across unrelated types",[101,1385,1386],{},[26,1387,709],{},[77,1389,1390,1393],{},[101,1391,1392],{},"Need to accept third-party classes without modifying them",[101,1394,1395],{},[26,1396,709],{},[77,1398,1399,1404],{},[101,1400,1401,1403],{},[26,1402,169],{}," checks at runtime are required",[101,1405,1406,1408,1409,128],{},[26,1407,318],{}," (or ",[26,1410,1411],{},"@runtime_checkable Protocol",[77,1413,1414,1417],{},[101,1415,1416],{},"Modern library\u002FAPI code with type checking",[101,1418,1419],{},[26,1420,709],{},[10,1422,1424],{"id":1423},"recap","Recap",[15,1426,1427,1430,1431,1433,1434,1437,1438,1440,1441,1443,1444,1446,1447,1449],{},[19,1428,1429],{},"ABCs"," use nominal subtyping — classes must explicitly inherit from the ABC and\nimplement every abstract method, which Python enforces at instantiation time. They support\nshared mixin implementations and integrate cleanly with ",[26,1432,589],{},". ",[19,1435,1436],{},"Protocols"," use\nstructural subtyping — any class with the right methods and attributes satisfies the\nprotocol without declaring it, which type checkers (mypy, pyright) verify statically. Add\n",[26,1439,147],{}," to get ",[26,1442,589],{}," support, though only method presence is checked.\nDefault to a ",[19,1445,709],{}," for library code and capability-based interfaces; reach for an\n",[19,1448,318],{}," when you need a tight class hierarchy, shared concrete methods, or reliable\nruntime enforcement.",[1451,1452,1453],"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":270,"searchDepth":298,"depth":298,"links":1455},[1456,1457,1458,1459,1460,1461,1462,1463,1464],{"id":12,"depth":298,"text":13},{"id":40,"depth":298,"text":41},{"id":68,"depth":298,"text":69},{"id":255,"depth":298,"text":256},{"id":658,"depth":298,"text":659},{"id":1014,"depth":298,"text":1015},{"id":1191,"depth":298,"text":1192},{"id":1345,"depth":298,"text":1346},{"id":1423,"depth":298,"text":1424},"Python ABC vs Protocol explained — nominal vs structural subtyping, runtime enforcement vs static checking, @runtime_checkable, and the decision rule for choosing between them in interviews and modern Python code.","hard","md","Python",{},"\u002Fblog\u002Fpython-abc-vs-protocol",{"title":5,"description":1465},"blog\u002Fpython-abc-vs-protocol","ABC vs Protocol — Nominal vs Structural Typing","Object-Oriented Programming","oop","2026-06-21","5x2Y-6UYaHafFq4oOafyaBZT9Dtkj1lJb0SgpqhE-Z4",1782244088048]