[{"data":1,"prerenderedAt":3189},["ShallowReactive",2],{"page-\u002Fpython\u002F23-metaprogramming":3},{"id":4,"title":5,"body":6,"description":27,"extension":3183,"meta":3184,"navigation":75,"path":3185,"seo":3186,"stem":3187,"__hash__":3188},"content\u002Fpython\u002F23-metaprogramming.md","23 — Metaprogramming",{"type":7,"value":8,"toc":3159},"minimark",[9,13,18,158,180,353,357,529,555,566,757,775,779,917,924,928,1142,1159,1238,1253,1266,1434,1464,1580,1586,1784,1818,1827,1958,1983,1990,2149,2164,2171,2390,2396,2400,2480,2484,2575,2579,2582,2879,3097,3101,3155],[10,11,5],"h1",{"id":12},"_23-metaprogramming",[14,15,17],"h2",{"id":16},"everything-is-an-object-even-classes","Everything Is an Object, Even Classes",[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","class Dog:\n    def bark(self):\n        return \"Woof!\"\n\nprint(type(Dog))          # \u003Cclass 'type'> — a CLASS is itself an instance of something: `type`\nprint(type(42))              # \u003Cclass 'int'>\nprint(type(int))               # \u003Cclass 'type'> — int, too, is an instance of type\nprint(isinstance(Dog, type))     # True\n","",[29,30,31,48,60,70,77,97,117,137],"code",{"__ignoreMap":27},[32,33,36,40,44],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"svdQ7","class",[32,41,43],{"class":42},"sIsaT"," Dog",[32,45,47],{"class":46},"ssxIu",":\n",[32,49,51,54,57],{"class":34,"line":50},2,[32,52,53],{"class":38},"    def",[32,55,56],{"class":42}," bark",[32,58,59],{"class":46},"(self):\n",[32,61,63,66],{"class":34,"line":62},3,[32,64,65],{"class":38},"        return",[32,67,69],{"class":68},"sJ6F3"," \"Woof!\"\n",[32,71,73],{"class":34,"line":72},4,[32,74,76],{"emptyLinePlaceholder":75},true,"\n",[32,78,80,84,87,90,93],{"class":34,"line":79},5,[32,81,83],{"class":82},"snvgF","print",[32,85,86],{"class":46},"(",[32,88,89],{"class":82},"type",[32,91,92],{"class":46},"(Dog))          ",[32,94,96],{"class":95},"sdCPZ","# \u003Cclass 'type'> — a CLASS is itself an instance of something: `type`\n",[32,98,100,102,104,106,108,111,114],{"class":34,"line":99},6,[32,101,83],{"class":82},[32,103,86],{"class":46},[32,105,89],{"class":82},[32,107,86],{"class":46},[32,109,110],{"class":82},"42",[32,112,113],{"class":46},"))              ",[32,115,116],{"class":95},"# \u003Cclass 'int'>\n",[32,118,120,122,124,126,128,131,134],{"class":34,"line":119},7,[32,121,83],{"class":82},[32,123,86],{"class":46},[32,125,89],{"class":82},[32,127,86],{"class":46},[32,129,130],{"class":82},"int",[32,132,133],{"class":46},"))               ",[32,135,136],{"class":95},"# \u003Cclass 'type'> — int, too, is an instance of type\n",[32,138,140,142,144,147,150,152,155],{"class":34,"line":139},8,[32,141,83],{"class":82},[32,143,86],{"class":46},[32,145,146],{"class":82},"isinstance",[32,148,149],{"class":46},"(Dog, ",[32,151,89],{"class":82},[32,153,154],{"class":46},"))     ",[32,156,157],{"class":95},"# True\n",[159,160,161,162,164,165,168,169,172,173,176,177,179],"p",{},"In Python, classes are ordinary objects, and ",[29,163,89],{}," is the class of every class — this is the foundation metaprogramming builds on. Just as ",[29,166,167],{},"Dog()"," creates an instance of ",[29,170,171],{},"Dog",", ",[29,174,175],{},"type(...)"," (called with three arguments) creates an instance of ",[29,178,89],{},", i.e., a brand new class, entirely at runtime:",[19,181,182],{"language":21},[23,183,185],{"className":25,"code":184,"language":21,"meta":27,"style":27},"def bark(self):\n    return \"Woof!\"\n\nDog = type(\"Dog\", (), {\"bark\": bark})    # equivalent to `class Dog: def bark(self): ...`\nd = Dog()\nprint(d.bark())    # \"Woof!\"\nprint(type(Dog))     # \u003Cclass 'type'>\n\n# type(name, bases, namespace) — the three arguments mirror a class statement exactly:\nAnimal = type(\"Animal\", (), {})\nCat = type(\"Cat\", (Animal,), {\"sound\": lambda self: \"Meow!\"})   # bases=(Animal,) — real inheritance\nprint(Cat.__mro__)   # (\u003Cclass 'Cat'>, \u003Cclass 'Animal'>, \u003Cclass 'object'>)\n",[29,186,187,196,203,207,235,245,255,269,273,279,297,336],{"__ignoreMap":27},[32,188,189,192,194],{"class":34,"line":35},[32,190,191],{"class":38},"def",[32,193,56],{"class":42},[32,195,59],{"class":46},[32,197,198,201],{"class":34,"line":50},[32,199,200],{"class":38},"    return",[32,202,69],{"class":68},[32,204,205],{"class":34,"line":62},[32,206,76],{"emptyLinePlaceholder":75},[32,208,209,212,215,218,220,223,226,229,232],{"class":34,"line":72},[32,210,211],{"class":46},"Dog ",[32,213,214],{"class":38},"=",[32,216,217],{"class":82}," type",[32,219,86],{"class":46},[32,221,222],{"class":68},"\"Dog\"",[32,224,225],{"class":46},", (), {",[32,227,228],{"class":68},"\"bark\"",[32,230,231],{"class":46},": bark})    ",[32,233,234],{"class":95},"# equivalent to `class Dog: def bark(self): ...`\n",[32,236,237,240,242],{"class":34,"line":79},[32,238,239],{"class":46},"d ",[32,241,214],{"class":38},[32,243,244],{"class":46}," Dog()\n",[32,246,247,249,252],{"class":34,"line":99},[32,248,83],{"class":82},[32,250,251],{"class":46},"(d.bark())    ",[32,253,254],{"class":95},"# \"Woof!\"\n",[32,256,257,259,261,263,266],{"class":34,"line":119},[32,258,83],{"class":82},[32,260,86],{"class":46},[32,262,89],{"class":82},[32,264,265],{"class":46},"(Dog))     ",[32,267,268],{"class":95},"# \u003Cclass 'type'>\n",[32,270,271],{"class":34,"line":139},[32,272,76],{"emptyLinePlaceholder":75},[32,274,276],{"class":34,"line":275},9,[32,277,278],{"class":95},"# type(name, bases, namespace) — the three arguments mirror a class statement exactly:\n",[32,280,282,285,287,289,291,294],{"class":34,"line":281},10,[32,283,284],{"class":46},"Animal ",[32,286,214],{"class":38},[32,288,217],{"class":82},[32,290,86],{"class":46},[32,292,293],{"class":68},"\"Animal\"",[32,295,296],{"class":46},", (), {})\n",[32,298,300,303,305,307,309,312,315,318,321,324,327,330,333],{"class":34,"line":299},11,[32,301,302],{"class":46},"Cat ",[32,304,214],{"class":38},[32,306,217],{"class":82},[32,308,86],{"class":46},[32,310,311],{"class":68},"\"Cat\"",[32,313,314],{"class":46},", (Animal,), {",[32,316,317],{"class":68},"\"sound\"",[32,319,320],{"class":46},": ",[32,322,323],{"class":38},"lambda",[32,325,326],{"class":46}," self: ",[32,328,329],{"class":68},"\"Meow!\"",[32,331,332],{"class":46},"})   ",[32,334,335],{"class":95},"# bases=(Animal,) — real inheritance\n",[32,337,339,341,344,347,350],{"class":34,"line":338},12,[32,340,83],{"class":82},[32,342,343],{"class":46},"(Cat.",[32,345,346],{"class":82},"__mro__",[32,348,349],{"class":46},")   ",[32,351,352],{"class":95},"# (\u003Cclass 'Cat'>, \u003Cclass 'Animal'>, \u003Cclass 'object'>)\n",[14,354,356],{"id":355},"metaclasses-classes-that-create-classes","Metaclasses: Classes That Create Classes",[19,358,359],{"language":21},[23,360,362],{"className":25,"code":361,"language":21,"meta":27,"style":27},"class UppercaseAttrMeta(type):\n    def __new__(mcs, name, bases, namespace):\n        uppercase_namespace = {\n            (key.upper() if not key.startswith(\"__\") else key): value\n            for key, value in namespace.items()\n        }\n        return super().__new__(mcs, name, bases, uppercase_namespace)\n\nclass Config(metaclass=UppercaseAttrMeta):\n    debug = True\n    version = \"1.0\"\n\nprint(Config.DEBUG)      # True — 'debug' was rewritten to 'DEBUG' at CLASS CREATION time\n# print(Config.debug)     # AttributeError: type object 'Config' has no attribute 'debug'\n",[29,363,364,378,388,398,424,438,443,459,463,482,492,502,506,523],{"__ignoreMap":27},[32,365,366,368,371,373,375],{"class":34,"line":35},[32,367,39],{"class":38},[32,369,370],{"class":42}," UppercaseAttrMeta",[32,372,86],{"class":46},[32,374,89],{"class":82},[32,376,377],{"class":46},"):\n",[32,379,380,382,385],{"class":34,"line":50},[32,381,53],{"class":38},[32,383,384],{"class":82}," __new__",[32,386,387],{"class":46},"(mcs, name, bases, namespace):\n",[32,389,390,393,395],{"class":34,"line":62},[32,391,392],{"class":46},"        uppercase_namespace ",[32,394,214],{"class":38},[32,396,397],{"class":46}," {\n",[32,399,400,403,406,409,412,415,418,421],{"class":34,"line":72},[32,401,402],{"class":46},"            (key.upper() ",[32,404,405],{"class":38},"if",[32,407,408],{"class":38}," not",[32,410,411],{"class":46}," key.startswith(",[32,413,414],{"class":68},"\"__\"",[32,416,417],{"class":46},") ",[32,419,420],{"class":38},"else",[32,422,423],{"class":46}," key): value\n",[32,425,426,429,432,435],{"class":34,"line":79},[32,427,428],{"class":38},"            for",[32,430,431],{"class":46}," key, value ",[32,433,434],{"class":38},"in",[32,436,437],{"class":46}," namespace.items()\n",[32,439,440],{"class":34,"line":99},[32,441,442],{"class":46},"        }\n",[32,444,445,447,450,453,456],{"class":34,"line":119},[32,446,65],{"class":38},[32,448,449],{"class":82}," super",[32,451,452],{"class":46},"().",[32,454,455],{"class":82},"__new__",[32,457,458],{"class":46},"(mcs, name, bases, uppercase_namespace)\n",[32,460,461],{"class":34,"line":139},[32,462,76],{"emptyLinePlaceholder":75},[32,464,465,467,470,472,475,477,480],{"class":34,"line":275},[32,466,39],{"class":38},[32,468,469],{"class":42}," Config",[32,471,86],{"class":46},[32,473,474],{"class":82},"metaclass",[32,476,214],{"class":38},[32,478,479],{"class":42},"UppercaseAttrMeta",[32,481,377],{"class":46},[32,483,484,487,489],{"class":34,"line":281},[32,485,486],{"class":46},"    debug ",[32,488,214],{"class":38},[32,490,491],{"class":82}," True\n",[32,493,494,497,499],{"class":34,"line":299},[32,495,496],{"class":46},"    version ",[32,498,214],{"class":38},[32,500,501],{"class":68}," \"1.0\"\n",[32,503,504],{"class":34,"line":338},[32,505,76],{"emptyLinePlaceholder":75},[32,507,509,511,514,517,520],{"class":34,"line":508},13,[32,510,83],{"class":82},[32,512,513],{"class":46},"(Config.",[32,515,516],{"class":82},"DEBUG",[32,518,519],{"class":46},")      ",[32,521,522],{"class":95},"# True — 'debug' was rewritten to 'DEBUG' at CLASS CREATION time\n",[32,524,526],{"class":34,"line":525},14,[32,527,528],{"class":95},"# print(Config.debug)     # AttributeError: type object 'Config' has no attribute 'debug'\n",[159,530,531,532,535,536,540,541,544,545,548,549,551,552,554],{},"A ",[533,534,474],"strong",{}," is simply \"the class of a class\" — just as a class controls the construction and behavior of its instances, a metaclass controls the construction and behavior of ",[537,538,539],"em",{},"classes"," that use it. ",[29,542,543],{},"metaclass=UppercaseAttrMeta"," tells Python \"when building the ",[29,546,547],{},"Config"," class object itself, run it through ",[29,550,479],{}," instead of the default ",[29,553,89],{},".\" This runs once, when the class statement itself is executed (typically at import time) — not per-instance.",[556,557,559,561,562,565],"h3",{"id":558},"__new__-vs-__init__-on-a-metaclass",[29,560,455],{}," vs ",[29,563,564],{},"__init__"," on a Metaclass",[19,567,568],{"language":21},[23,569,571],{"className":25,"code":570,"language":21,"meta":27,"style":27},"class LoggingMeta(type):\n    def __new__(mcs, name, bases, namespace):\n        print(f\"__new__: creating class object for {name}\")\n        cls = super().__new__(mcs, name, bases, namespace)\n        return cls\n\n    def __init__(cls, name, bases, namespace):\n        print(f\"__init__: initializing class object for {name}\")\n        super().__init__(name, bases, namespace)\n        cls.created_via_logging_meta = True\n\nclass Widget(metaclass=LoggingMeta):\n    pass\n# __new__: creating class object for Widget\n# __init__: initializing class object for Widget\n\nprint(Widget.created_via_logging_meta)   # True\n",[29,572,573,586,594,622,639,646,650,660,681,693,704,708,726,731,736,742,747],{"__ignoreMap":27},[32,574,575,577,580,582,584],{"class":34,"line":35},[32,576,39],{"class":38},[32,578,579],{"class":42}," LoggingMeta",[32,581,86],{"class":46},[32,583,89],{"class":82},[32,585,377],{"class":46},[32,587,588,590,592],{"class":34,"line":50},[32,589,53],{"class":38},[32,591,384],{"class":82},[32,593,387],{"class":46},[32,595,596,599,601,604,607,610,613,616,619],{"class":34,"line":62},[32,597,598],{"class":82},"        print",[32,600,86],{"class":46},[32,602,603],{"class":38},"f",[32,605,606],{"class":68},"\"__new__: creating class object for ",[32,608,609],{"class":82},"{",[32,611,612],{"class":46},"name",[32,614,615],{"class":82},"}",[32,617,618],{"class":68},"\"",[32,620,621],{"class":46},")\n",[32,623,624,627,630,632,634,636],{"class":34,"line":72},[32,625,626],{"class":82},"        cls",[32,628,629],{"class":38}," =",[32,631,449],{"class":82},[32,633,452],{"class":46},[32,635,455],{"class":82},[32,637,638],{"class":46},"(mcs, name, bases, namespace)\n",[32,640,641,643],{"class":34,"line":79},[32,642,65],{"class":38},[32,644,645],{"class":82}," cls\n",[32,647,648],{"class":34,"line":99},[32,649,76],{"emptyLinePlaceholder":75},[32,651,652,654,657],{"class":34,"line":119},[32,653,53],{"class":38},[32,655,656],{"class":82}," __init__",[32,658,659],{"class":46},"(cls, name, bases, namespace):\n",[32,661,662,664,666,668,671,673,675,677,679],{"class":34,"line":139},[32,663,598],{"class":82},[32,665,86],{"class":46},[32,667,603],{"class":38},[32,669,670],{"class":68},"\"__init__: initializing class object for ",[32,672,609],{"class":82},[32,674,612],{"class":46},[32,676,615],{"class":82},[32,678,618],{"class":68},[32,680,621],{"class":46},[32,682,683,686,688,690],{"class":34,"line":275},[32,684,685],{"class":82},"        super",[32,687,452],{"class":46},[32,689,564],{"class":82},[32,691,692],{"class":46},"(name, bases, namespace)\n",[32,694,695,697,700,702],{"class":34,"line":281},[32,696,626],{"class":82},[32,698,699],{"class":46},".created_via_logging_meta ",[32,701,214],{"class":38},[32,703,491],{"class":82},[32,705,706],{"class":34,"line":299},[32,707,76],{"emptyLinePlaceholder":75},[32,709,710,712,715,717,719,721,724],{"class":34,"line":338},[32,711,39],{"class":38},[32,713,714],{"class":42}," Widget",[32,716,86],{"class":46},[32,718,474],{"class":82},[32,720,214],{"class":38},[32,722,723],{"class":42},"LoggingMeta",[32,725,377],{"class":46},[32,727,728],{"class":34,"line":508},[32,729,730],{"class":38},"    pass\n",[32,732,733],{"class":34,"line":525},[32,734,735],{"class":95},"# __new__: creating class object for Widget\n",[32,737,739],{"class":34,"line":738},15,[32,740,741],{"class":95},"# __init__: initializing class object for Widget\n",[32,743,745],{"class":34,"line":744},16,[32,746,76],{"emptyLinePlaceholder":75},[32,748,750,752,755],{"class":34,"line":749},17,[32,751,83],{"class":82},[32,753,754],{"class":46},"(Widget.created_via_logging_meta)   ",[32,756,157],{"class":95},[159,758,759,761,762,765,766,768,769,771,772,774],{},[29,760,455],{}," is responsible for ",[537,763,764],{},"creating and returning"," the class object; ",[29,767,564],{}," receives the already-created object and can further configure it (add attributes, validate) but cannot change its identity or type. This exactly mirrors the instance-level ",[29,770,455],{},"\u002F",[29,773,564],{}," split (chapter 12), one level up the metaclass hierarchy.",[556,776,778],{"id":777},"a-real-world-use-enforcing-an-interface-at-class-creation-time","A real-world use: enforcing an interface at class-creation time",[19,780,781],{"language":21},[23,782,784],{"className":25,"code":783,"language":21,"meta":27,"style":27},"class EnforceInterfaceMeta(type):\n    def __new__(mcs, name, bases, namespace):\n        cls = super().__new__(mcs, name, bases, namespace)\n        if bases and \"process\" not in namespace:            # skip the base class itself\n            raise TypeError(f\"{name} must implement a 'process' method\")\n        return cls\n\nclass Handler(metaclass=EnforceInterfaceMeta):\n    pass\n\n# class BrokenHandler(Handler):     # raises TypeError IMMEDIATELY at class definition time —\n#     pass                            # not when an instance is created, and not when process() is called\n",[29,785,786,799,807,821,846,871,877,881,899,903,907,912],{"__ignoreMap":27},[32,787,788,790,793,795,797],{"class":34,"line":35},[32,789,39],{"class":38},[32,791,792],{"class":42}," EnforceInterfaceMeta",[32,794,86],{"class":46},[32,796,89],{"class":82},[32,798,377],{"class":46},[32,800,801,803,805],{"class":34,"line":50},[32,802,53],{"class":38},[32,804,384],{"class":82},[32,806,387],{"class":46},[32,808,809,811,813,815,817,819],{"class":34,"line":62},[32,810,626],{"class":82},[32,812,629],{"class":38},[32,814,449],{"class":82},[32,816,452],{"class":46},[32,818,455],{"class":82},[32,820,638],{"class":46},[32,822,823,826,829,832,835,837,840,843],{"class":34,"line":72},[32,824,825],{"class":38},"        if",[32,827,828],{"class":46}," bases ",[32,830,831],{"class":38},"and",[32,833,834],{"class":68}," \"process\"",[32,836,408],{"class":38},[32,838,839],{"class":38}," in",[32,841,842],{"class":46}," namespace:            ",[32,844,845],{"class":95},"# skip the base class itself\n",[32,847,848,851,854,856,858,860,862,864,866,869],{"class":34,"line":79},[32,849,850],{"class":38},"            raise",[32,852,853],{"class":82}," TypeError",[32,855,86],{"class":46},[32,857,603],{"class":38},[32,859,618],{"class":68},[32,861,609],{"class":82},[32,863,612],{"class":46},[32,865,615],{"class":82},[32,867,868],{"class":68}," must implement a 'process' method\"",[32,870,621],{"class":46},[32,872,873,875],{"class":34,"line":99},[32,874,65],{"class":38},[32,876,645],{"class":82},[32,878,879],{"class":34,"line":119},[32,880,76],{"emptyLinePlaceholder":75},[32,882,883,885,888,890,892,894,897],{"class":34,"line":139},[32,884,39],{"class":38},[32,886,887],{"class":42}," Handler",[32,889,86],{"class":46},[32,891,474],{"class":82},[32,893,214],{"class":38},[32,895,896],{"class":42},"EnforceInterfaceMeta",[32,898,377],{"class":46},[32,900,901],{"class":34,"line":275},[32,902,730],{"class":38},[32,904,905],{"class":34,"line":281},[32,906,76],{"emptyLinePlaceholder":75},[32,908,909],{"class":34,"line":299},[32,910,911],{"class":95},"# class BrokenHandler(Handler):     # raises TypeError IMMEDIATELY at class definition time —\n",[32,913,914],{"class":34,"line":338},[32,915,916],{"class":95},"#     pass                            # not when an instance is created, and not when process() is called\n",[159,918,919,920,923],{},"This is metaprogramming's real payoff: catching a whole category of mistakes (a missing required method) the moment the class is ",[537,921,922],{},"defined",", at import time, rather than waiting for the bug to surface later at instantiation or call time — often in production, far from the actual missing-method mistake.",[14,925,927],{"id":926},"class-decorators-a-simpler-alternative-to-metaclasses","Class Decorators: A Simpler Alternative to Metaclasses",[19,929,930],{"language":21},[23,931,933],{"className":25,"code":932,"language":21,"meta":27,"style":27},"def add_repr(cls):\n    def __repr__(self):\n        attrs = \", \".join(f\"{k}={v!r}\" for k, v in self.__dict__.items())\n        return f\"{cls.__name__}({attrs})\"\n    cls.__repr__ = __repr__\n    return cls\n\n@add_repr\nclass Point:\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\np = Point(1, 2)\nprint(p)   # Point(x=1, y=2)\n",[29,934,935,945,954,1012,1041,1056,1062,1066,1071,1080,1089,1108,1112,1132],{"__ignoreMap":27},[32,936,937,939,942],{"class":34,"line":35},[32,938,191],{"class":38},[32,940,941],{"class":42}," add_repr",[32,943,944],{"class":46},"(cls):\n",[32,946,947,949,952],{"class":34,"line":50},[32,948,53],{"class":38},[32,950,951],{"class":82}," __repr__",[32,953,59],{"class":46},[32,955,956,959,961,964,967,969,971,973,976,978,980,982,985,988,990,992,995,998,1000,1003,1006,1009],{"class":34,"line":62},[32,957,958],{"class":46},"        attrs ",[32,960,214],{"class":38},[32,962,963],{"class":68}," \", \"",[32,965,966],{"class":46},".join(",[32,968,603],{"class":38},[32,970,618],{"class":68},[32,972,609],{"class":82},[32,974,975],{"class":46},"k",[32,977,615],{"class":82},[32,979,214],{"class":68},[32,981,609],{"class":82},[32,983,984],{"class":46},"v",[32,986,987],{"class":38},"!r",[32,989,615],{"class":82},[32,991,618],{"class":68},[32,993,994],{"class":38}," for",[32,996,997],{"class":46}," k, v ",[32,999,434],{"class":38},[32,1001,1002],{"class":82}," self",[32,1004,1005],{"class":46},".",[32,1007,1008],{"class":82},"__dict__",[32,1010,1011],{"class":46},".items())\n",[32,1013,1014,1016,1019,1021,1024,1026,1029,1031,1033,1036,1038],{"class":34,"line":72},[32,1015,65],{"class":38},[32,1017,1018],{"class":38}," f",[32,1020,618],{"class":68},[32,1022,1023],{"class":82},"{cls",[32,1025,1005],{"class":46},[32,1027,1028],{"class":82},"__name__}",[32,1030,86],{"class":68},[32,1032,609],{"class":82},[32,1034,1035],{"class":46},"attrs",[32,1037,615],{"class":82},[32,1039,1040],{"class":68},")\"\n",[32,1042,1043,1046,1048,1051,1053],{"class":34,"line":79},[32,1044,1045],{"class":82},"    cls",[32,1047,1005],{"class":46},[32,1049,1050],{"class":82},"__repr__",[32,1052,629],{"class":38},[32,1054,1055],{"class":82}," __repr__\n",[32,1057,1058,1060],{"class":34,"line":99},[32,1059,200],{"class":38},[32,1061,645],{"class":82},[32,1063,1064],{"class":34,"line":119},[32,1065,76],{"emptyLinePlaceholder":75},[32,1067,1068],{"class":34,"line":139},[32,1069,1070],{"class":42},"@add_repr\n",[32,1072,1073,1075,1078],{"class":34,"line":275},[32,1074,39],{"class":38},[32,1076,1077],{"class":42}," Point",[32,1079,47],{"class":46},[32,1081,1082,1084,1086],{"class":34,"line":281},[32,1083,53],{"class":38},[32,1085,656],{"class":82},[32,1087,1088],{"class":46},"(self, x, y):\n",[32,1090,1091,1094,1097,1100,1103,1105],{"class":34,"line":299},[32,1092,1093],{"class":82},"        self",[32,1095,1096],{"class":46},".x, ",[32,1098,1099],{"class":82},"self",[32,1101,1102],{"class":46},".y ",[32,1104,214],{"class":38},[32,1106,1107],{"class":46}," x, y\n",[32,1109,1110],{"class":34,"line":338},[32,1111,76],{"emptyLinePlaceholder":75},[32,1113,1114,1117,1119,1122,1125,1127,1130],{"class":34,"line":508},[32,1115,1116],{"class":46},"p ",[32,1118,214],{"class":38},[32,1120,1121],{"class":46}," Point(",[32,1123,1124],{"class":82},"1",[32,1126,172],{"class":46},[32,1128,1129],{"class":82},"2",[32,1131,621],{"class":46},[32,1133,1134,1136,1139],{"class":34,"line":525},[32,1135,83],{"class":82},[32,1137,1138],{"class":46},"(p)   ",[32,1140,1141],{"class":95},"# Point(x=1, y=2)\n",[159,1143,1144,1145,1147,1148,1150,1151,1154,1155,1158],{},"A class decorator receives the already-fully-built class object and can modify or wrap it — mechanically much simpler than a metaclass (no ",[29,1146,89],{}," subclassing, no ",[29,1149,455],{}," override) and sufficient for the vast majority of \"customize class creation\" needs. ",[533,1152,1153],{},"Best practice",": reach for a class decorator before a metaclass — metaclasses don't compose well (two unrelated metaclasses can't easily both apply to the same class) and are harder for other developers to reason about; use one only when you specifically need to intercept the class-creation process itself (e.g., validating ",[29,1156,1157],{},"bases",", rewriting the namespace before the class exists) rather than merely modifying the finished product.",[19,1160,1161],{"language":21},[23,1162,1164],{"className":25,"code":1163,"language":21,"meta":27,"style":27},"from dataclasses import dataclass    # the standard library's own class decorator — chapter 12 territory\n\n@dataclass\nclass Point:\n    x: int\n    y: int\n\nprint(Point(1, 2))   # Point(x=1, y=2) — auto-generated __init__, __repr__, __eq__\n",[29,1165,1166,1183,1187,1192,1200,1208,1215,1219],{"__ignoreMap":27},[32,1167,1168,1171,1174,1177,1180],{"class":34,"line":35},[32,1169,1170],{"class":38},"from",[32,1172,1173],{"class":46}," dataclasses ",[32,1175,1176],{"class":38},"import",[32,1178,1179],{"class":46}," dataclass    ",[32,1181,1182],{"class":95},"# the standard library's own class decorator — chapter 12 territory\n",[32,1184,1185],{"class":34,"line":50},[32,1186,76],{"emptyLinePlaceholder":75},[32,1188,1189],{"class":34,"line":62},[32,1190,1191],{"class":42},"@dataclass\n",[32,1193,1194,1196,1198],{"class":34,"line":72},[32,1195,39],{"class":38},[32,1197,1077],{"class":42},[32,1199,47],{"class":46},[32,1201,1202,1205],{"class":34,"line":79},[32,1203,1204],{"class":46},"    x: ",[32,1206,1207],{"class":82},"int\n",[32,1209,1210,1213],{"class":34,"line":99},[32,1211,1212],{"class":46},"    y: ",[32,1214,1207],{"class":82},[32,1216,1217],{"class":34,"line":119},[32,1218,76],{"emptyLinePlaceholder":75},[32,1220,1221,1223,1226,1228,1230,1232,1235],{"class":34,"line":139},[32,1222,83],{"class":82},[32,1224,1225],{"class":46},"(Point(",[32,1227,1124],{"class":82},[32,1229,172],{"class":46},[32,1231,1129],{"class":82},[32,1233,1234],{"class":46},"))   ",[32,1236,1237],{"class":95},"# Point(x=1, y=2) — auto-generated __init__, __repr__, __eq__\n",[159,1239,1240,1243,1244,172,1246,1248,1249,1252],{},[29,1241,1242],{},"@dataclass"," itself is a class decorator — one of the most widely used pieces of metaprogramming in the entire standard library, generating ",[29,1245,564],{},[29,1247,1050],{},", and ",[29,1250,1251],{},"__eq__"," by inspecting class-level annotations.",[14,1254,1256,1257,172,1260,172,1263],{"id":1255},"dynamic-attribute-access-__getattr__-__setattr__-__getattribute__","Dynamic Attribute Access: ",[29,1258,1259],{},"__getattr__",[29,1261,1262],{},"__setattr__",[29,1264,1265],{},"__getattribute__",[19,1267,1268],{"language":21},[23,1269,1271],{"className":25,"code":1270,"language":21,"meta":27,"style":27},"class LazyConfig:\n    def __init__(self):\n        self._loaded = {}\n\n    def __getattr__(self, name):\n        # ONLY called when normal attribute lookup FAILS (attribute not found via __dict__\u002Fclass)\n        print(f\"loading {name} on demand...\")\n        value = f\"value-for-{name}\"    # imagine a real config-file lookup here\n        self._loaded[name] = value\n        return value\n\nconfig = LazyConfig()\nprint(config.database_url)   # \"loading database_url on demand...\" then \"value-for-database_url\"\nprint(config._loaded)          # {'database_url': 'value-for-database_url'}\nprint(config.database_url)       # NOTE: still triggers __getattr__ again! it was never stored as a REAL attribute\n",[29,1272,1273,1282,1290,1302,1306,1316,1321,1343,1366,1378,1384,1388,1398,1408,1418],{"__ignoreMap":27},[32,1274,1275,1277,1280],{"class":34,"line":35},[32,1276,39],{"class":38},[32,1278,1279],{"class":42}," LazyConfig",[32,1281,47],{"class":46},[32,1283,1284,1286,1288],{"class":34,"line":50},[32,1285,53],{"class":38},[32,1287,656],{"class":82},[32,1289,59],{"class":46},[32,1291,1292,1294,1297,1299],{"class":34,"line":62},[32,1293,1093],{"class":82},[32,1295,1296],{"class":46},"._loaded ",[32,1298,214],{"class":38},[32,1300,1301],{"class":46}," {}\n",[32,1303,1304],{"class":34,"line":72},[32,1305,76],{"emptyLinePlaceholder":75},[32,1307,1308,1310,1313],{"class":34,"line":79},[32,1309,53],{"class":38},[32,1311,1312],{"class":82}," __getattr__",[32,1314,1315],{"class":46},"(self, name):\n",[32,1317,1318],{"class":34,"line":99},[32,1319,1320],{"class":95},"        # ONLY called when normal attribute lookup FAILS (attribute not found via __dict__\u002Fclass)\n",[32,1322,1323,1325,1327,1329,1332,1334,1336,1338,1341],{"class":34,"line":119},[32,1324,598],{"class":82},[32,1326,86],{"class":46},[32,1328,603],{"class":38},[32,1330,1331],{"class":68},"\"loading ",[32,1333,609],{"class":82},[32,1335,612],{"class":46},[32,1337,615],{"class":82},[32,1339,1340],{"class":68}," on demand...\"",[32,1342,621],{"class":46},[32,1344,1345,1348,1350,1352,1355,1357,1359,1361,1363],{"class":34,"line":139},[32,1346,1347],{"class":46},"        value ",[32,1349,214],{"class":38},[32,1351,1018],{"class":38},[32,1353,1354],{"class":68},"\"value-for-",[32,1356,609],{"class":82},[32,1358,612],{"class":46},[32,1360,615],{"class":82},[32,1362,618],{"class":68},[32,1364,1365],{"class":95},"    # imagine a real config-file lookup here\n",[32,1367,1368,1370,1373,1375],{"class":34,"line":275},[32,1369,1093],{"class":82},[32,1371,1372],{"class":46},"._loaded[name] ",[32,1374,214],{"class":38},[32,1376,1377],{"class":46}," value\n",[32,1379,1380,1382],{"class":34,"line":281},[32,1381,65],{"class":38},[32,1383,1377],{"class":46},[32,1385,1386],{"class":34,"line":299},[32,1387,76],{"emptyLinePlaceholder":75},[32,1389,1390,1393,1395],{"class":34,"line":338},[32,1391,1392],{"class":46},"config ",[32,1394,214],{"class":38},[32,1396,1397],{"class":46}," LazyConfig()\n",[32,1399,1400,1402,1405],{"class":34,"line":508},[32,1401,83],{"class":82},[32,1403,1404],{"class":46},"(config.database_url)   ",[32,1406,1407],{"class":95},"# \"loading database_url on demand...\" then \"value-for-database_url\"\n",[32,1409,1410,1412,1415],{"class":34,"line":525},[32,1411,83],{"class":82},[32,1413,1414],{"class":46},"(config._loaded)          ",[32,1416,1417],{"class":95},"# {'database_url': 'value-for-database_url'}\n",[32,1419,1420,1422,1425,1428,1431],{"class":34,"line":738},[32,1421,83],{"class":82},[32,1423,1424],{"class":46},"(config.database_url)       ",[32,1426,1427],{"class":95},"# ",[32,1429,1430],{"class":38},"NOTE",[32,1432,1433],{"class":95},": still triggers __getattr__ again! it was never stored as a REAL attribute\n",[159,1435,1436,1438,1439,1442,1443,1445,1446,1449,1450,1452,1453,1456,1457,1460,1461,1005],{},[29,1437,1259],{}," is only invoked as a ",[533,1440,1441],{},"fallback",", when normal lookup (instance ",[29,1444,1008],{},", then class, then MRO) fails to find the attribute — it is not a universal interception point. The example above has a subtle bug: reading ",[29,1447,1448],{},"config.database_url"," a second time re-triggers ",[29,1451,1259],{}," because the value was cached into ",[29,1454,1455],{},"self._loaded",", a ",[537,1458,1459],{},"different"," dict, rather than actually set as ",[29,1462,1463],{},"self.database_url",[19,1465,1466],{"language":21},[23,1467,1469],{"className":25,"code":1468,"language":21,"meta":27,"style":27},"class LazyConfigFixed:\n    def __getattr__(self, name):\n        print(f\"loading {name} on demand...\")\n        value = f\"value-for-{name}\"\n        setattr(self, name, value)   # actually stores it as a real instance attribute this time\n        return value\n\nconfig = LazyConfigFixed()\nprint(config.database_url)   # triggers __getattr__: \"loading...\"\nprint(config.database_url)     # NO print this time — found directly in __dict__, __getattr__ never called\n",[29,1470,1471,1480,1488,1508,1527,1542,1548,1552,1561,1570],{"__ignoreMap":27},[32,1472,1473,1475,1478],{"class":34,"line":35},[32,1474,39],{"class":38},[32,1476,1477],{"class":42}," LazyConfigFixed",[32,1479,47],{"class":46},[32,1481,1482,1484,1486],{"class":34,"line":50},[32,1483,53],{"class":38},[32,1485,1312],{"class":82},[32,1487,1315],{"class":46},[32,1489,1490,1492,1494,1496,1498,1500,1502,1504,1506],{"class":34,"line":62},[32,1491,598],{"class":82},[32,1493,86],{"class":46},[32,1495,603],{"class":38},[32,1497,1331],{"class":68},[32,1499,609],{"class":82},[32,1501,612],{"class":46},[32,1503,615],{"class":82},[32,1505,1340],{"class":68},[32,1507,621],{"class":46},[32,1509,1510,1512,1514,1516,1518,1520,1522,1524],{"class":34,"line":72},[32,1511,1347],{"class":46},[32,1513,214],{"class":38},[32,1515,1018],{"class":38},[32,1517,1354],{"class":68},[32,1519,609],{"class":82},[32,1521,612],{"class":46},[32,1523,615],{"class":82},[32,1525,1526],{"class":68},"\"\n",[32,1528,1529,1532,1534,1536,1539],{"class":34,"line":79},[32,1530,1531],{"class":82},"        setattr",[32,1533,86],{"class":46},[32,1535,1099],{"class":82},[32,1537,1538],{"class":46},", name, value)   ",[32,1540,1541],{"class":95},"# actually stores it as a real instance attribute this time\n",[32,1543,1544,1546],{"class":34,"line":99},[32,1545,65],{"class":38},[32,1547,1377],{"class":46},[32,1549,1550],{"class":34,"line":119},[32,1551,76],{"emptyLinePlaceholder":75},[32,1553,1554,1556,1558],{"class":34,"line":139},[32,1555,1392],{"class":46},[32,1557,214],{"class":38},[32,1559,1560],{"class":46}," LazyConfigFixed()\n",[32,1562,1563,1565,1567],{"class":34,"line":275},[32,1564,83],{"class":82},[32,1566,1404],{"class":46},[32,1568,1569],{"class":95},"# triggers __getattr__: \"loading...\"\n",[32,1571,1572,1574,1577],{"class":34,"line":281},[32,1573,83],{"class":82},[32,1575,1576],{"class":46},"(config.database_url)     ",[32,1578,1579],{"class":95},"# NO print this time — found directly in __dict__, __getattr__ never called\n",[556,1581,1583,1585],{"id":1582},"__setattr__-intercepting-every-attribute-assignment",[29,1584,1262],{}," — intercepting every attribute assignment",[19,1587,1588],{"language":21},[23,1589,1591],{"className":25,"code":1590,"language":21,"meta":27,"style":27},"class FrozenAfterInit:\n    def __init__(self, x, y):\n        self.__dict__[\"x\"] = x         # must bypass __setattr__ during __init__ itself, or infinite recursion!\n        self.__dict__[\"y\"] = y\n        self._frozen = True\n\n    def __setattr__(self, name, value):\n        if getattr(self, \"_frozen\", False):\n            raise AttributeError(f\"{self.__class__.__name__} is frozen; cannot set '{name}'\")\n        object.__setattr__(self, name, value)\n\np = FrozenAfterInit(1, 2)\n# p.x = 99   # AttributeError: FrozenAfterInit is frozen; cannot set 'x'\n",[29,1592,1593,1602,1610,1635,1655,1666,1670,1680,1703,1742,1758,1762,1779],{"__ignoreMap":27},[32,1594,1595,1597,1600],{"class":34,"line":35},[32,1596,39],{"class":38},[32,1598,1599],{"class":42}," FrozenAfterInit",[32,1601,47],{"class":46},[32,1603,1604,1606,1608],{"class":34,"line":50},[32,1605,53],{"class":38},[32,1607,656],{"class":82},[32,1609,1088],{"class":46},[32,1611,1612,1614,1616,1618,1621,1624,1627,1629,1632],{"class":34,"line":62},[32,1613,1093],{"class":82},[32,1615,1005],{"class":46},[32,1617,1008],{"class":82},[32,1619,1620],{"class":46},"[",[32,1622,1623],{"class":68},"\"x\"",[32,1625,1626],{"class":46},"] ",[32,1628,214],{"class":38},[32,1630,1631],{"class":46}," x         ",[32,1633,1634],{"class":95},"# must bypass __setattr__ during __init__ itself, or infinite recursion!\n",[32,1636,1637,1639,1641,1643,1645,1648,1650,1652],{"class":34,"line":72},[32,1638,1093],{"class":82},[32,1640,1005],{"class":46},[32,1642,1008],{"class":82},[32,1644,1620],{"class":46},[32,1646,1647],{"class":68},"\"y\"",[32,1649,1626],{"class":46},[32,1651,214],{"class":38},[32,1653,1654],{"class":46}," y\n",[32,1656,1657,1659,1662,1664],{"class":34,"line":79},[32,1658,1093],{"class":82},[32,1660,1661],{"class":46},"._frozen ",[32,1663,214],{"class":38},[32,1665,491],{"class":82},[32,1667,1668],{"class":34,"line":99},[32,1669,76],{"emptyLinePlaceholder":75},[32,1671,1672,1674,1677],{"class":34,"line":119},[32,1673,53],{"class":38},[32,1675,1676],{"class":82}," __setattr__",[32,1678,1679],{"class":46},"(self, name, value):\n",[32,1681,1682,1684,1687,1689,1691,1693,1696,1698,1701],{"class":34,"line":139},[32,1683,825],{"class":38},[32,1685,1686],{"class":82}," getattr",[32,1688,86],{"class":46},[32,1690,1099],{"class":82},[32,1692,172],{"class":46},[32,1694,1695],{"class":68},"\"_frozen\"",[32,1697,172],{"class":46},[32,1699,1700],{"class":82},"False",[32,1702,377],{"class":46},[32,1704,1705,1707,1710,1712,1714,1716,1719,1721,1724,1726,1728,1731,1733,1735,1737,1740],{"class":34,"line":275},[32,1706,850],{"class":38},[32,1708,1709],{"class":82}," AttributeError",[32,1711,86],{"class":46},[32,1713,603],{"class":38},[32,1715,618],{"class":68},[32,1717,1718],{"class":82},"{self",[32,1720,1005],{"class":46},[32,1722,1723],{"class":82},"__class__",[32,1725,1005],{"class":46},[32,1727,1028],{"class":82},[32,1729,1730],{"class":68}," is frozen; cannot set '",[32,1732,609],{"class":82},[32,1734,612],{"class":46},[32,1736,615],{"class":82},[32,1738,1739],{"class":68},"'\"",[32,1741,621],{"class":46},[32,1743,1744,1747,1749,1751,1753,1755],{"class":34,"line":281},[32,1745,1746],{"class":82},"        object",[32,1748,1005],{"class":46},[32,1750,1262],{"class":82},[32,1752,86],{"class":46},[32,1754,1099],{"class":82},[32,1756,1757],{"class":46},", name, value)\n",[32,1759,1760],{"class":34,"line":299},[32,1761,76],{"emptyLinePlaceholder":75},[32,1763,1764,1766,1768,1771,1773,1775,1777],{"class":34,"line":338},[32,1765,1116],{"class":46},[32,1767,214],{"class":38},[32,1769,1770],{"class":46}," FrozenAfterInit(",[32,1772,1124],{"class":82},[32,1774,172],{"class":46},[32,1776,1129],{"class":82},[32,1778,621],{"class":46},[32,1780,1781],{"class":34,"line":508},[32,1782,1783],{"class":95},"# p.x = 99   # AttributeError: FrozenAfterInit is frozen; cannot set 'x'\n",[159,1785,1786,1787,172,1789,1791,1792,1795,1796,1798,1799,1802,1803,1805,1806,1808,1809,1808,1811,1813,1814,1817],{},"Unlike ",[29,1788,1259],{},[29,1790,1262],{}," intercepts ",[533,1793,1794],{},"every"," attribute assignment unconditionally, including ones inside ",[29,1797,564],{}," — writing ",[29,1800,1801],{},"self.x = x"," naively inside ",[29,1804,1262],{},"'s own class would recurse infinitely (",[29,1807,1262],{}," calling ",[29,1810,1801],{},[29,1812,1262],{}," again...). The fix is always routing internal, \"real\" assignments through ",[29,1815,1816],{},"object.__setattr__(self, name, value)"," directly, bypassing the custom override.",[556,1819,1821,1823,1824,1826],{"id":1820},"__getattribute__-intercepting-every-attribute-read-found-or-not",[29,1822,1265],{}," — intercepting ",[537,1825,1794],{}," attribute read, found or not",[19,1828,1829],{"language":21},[23,1830,1832],{"className":25,"code":1831,"language":21,"meta":27,"style":27},"class LoggingAccess:\n    def __init__(self, value):\n        self.value = value\n\n    def __getattribute__(self, name):\n        print(f\"accessing '{name}'\")\n        return object.__getattribute__(self, name)     # delegate to the REAL lookup, or infinite recursion\n\nobj = LoggingAccess(42)\nprint(obj.value)\n# accessing 'value'\n# accessing 'value'   (once for the __getattribute__ call machinery, once for actual access, implementation detail)\n# 42\n",[29,1833,1834,1843,1852,1863,1867,1876,1897,1918,1922,1936,1943,1948,1953],{"__ignoreMap":27},[32,1835,1836,1838,1841],{"class":34,"line":35},[32,1837,39],{"class":38},[32,1839,1840],{"class":42}," LoggingAccess",[32,1842,47],{"class":46},[32,1844,1845,1847,1849],{"class":34,"line":50},[32,1846,53],{"class":38},[32,1848,656],{"class":82},[32,1850,1851],{"class":46},"(self, value):\n",[32,1853,1854,1856,1859,1861],{"class":34,"line":62},[32,1855,1093],{"class":82},[32,1857,1858],{"class":46},".value ",[32,1860,214],{"class":38},[32,1862,1377],{"class":46},[32,1864,1865],{"class":34,"line":72},[32,1866,76],{"emptyLinePlaceholder":75},[32,1868,1869,1871,1874],{"class":34,"line":79},[32,1870,53],{"class":38},[32,1872,1873],{"class":82}," __getattribute__",[32,1875,1315],{"class":46},[32,1877,1878,1880,1882,1884,1887,1889,1891,1893,1895],{"class":34,"line":99},[32,1879,598],{"class":82},[32,1881,86],{"class":46},[32,1883,603],{"class":38},[32,1885,1886],{"class":68},"\"accessing '",[32,1888,609],{"class":82},[32,1890,612],{"class":46},[32,1892,615],{"class":82},[32,1894,1739],{"class":68},[32,1896,621],{"class":46},[32,1898,1899,1901,1904,1906,1908,1910,1912,1915],{"class":34,"line":119},[32,1900,65],{"class":38},[32,1902,1903],{"class":82}," object",[32,1905,1005],{"class":46},[32,1907,1265],{"class":82},[32,1909,86],{"class":46},[32,1911,1099],{"class":82},[32,1913,1914],{"class":46},", name)     ",[32,1916,1917],{"class":95},"# delegate to the REAL lookup, or infinite recursion\n",[32,1919,1920],{"class":34,"line":139},[32,1921,76],{"emptyLinePlaceholder":75},[32,1923,1924,1927,1929,1932,1934],{"class":34,"line":275},[32,1925,1926],{"class":46},"obj ",[32,1928,214],{"class":38},[32,1930,1931],{"class":46}," LoggingAccess(",[32,1933,110],{"class":82},[32,1935,621],{"class":46},[32,1937,1938,1940],{"class":34,"line":281},[32,1939,83],{"class":82},[32,1941,1942],{"class":46},"(obj.value)\n",[32,1944,1945],{"class":34,"line":299},[32,1946,1947],{"class":95},"# accessing 'value'\n",[32,1949,1950],{"class":34,"line":338},[32,1951,1952],{"class":95},"# accessing 'value'   (once for the __getattribute__ call machinery, once for actual access, implementation detail)\n",[32,1954,1955],{"class":34,"line":508},[32,1956,1957],{"class":95},"# 42\n",[159,1959,1960,1962,1963,1966,1967,1969,1970,1972,1973,1975,1976,1978,1979,1982],{},[29,1961,1265],{}," is called for ",[533,1964,1965],{},"every single attribute access",", whether or not the attribute exists — far more powerful (and far easier to break) than ",[29,1968,1259],{},". ",[533,1971,1153],{},": override ",[29,1974,1259],{}," for \"handle missing attributes\" use cases (lazy loading, proxies to another object); reserve ",[29,1977,1265],{}," for genuinely universal interception (logging every access, strict sandboxing) and always delegate to ",[29,1980,1981],{},"object.__getattribute__"," to avoid infinite recursion.",[14,1984,1986,1989],{"id":1985},"__init_subclass__-a-lightweight-alternative-to-metaclasses",[29,1987,1988],{},"__init_subclass__"," — A Lightweight Alternative to Metaclasses",[19,1991,1992],{"language":21},[23,1993,1995],{"className":25,"code":1994,"language":21,"meta":27,"style":27},"class PluginBase:\n    registry = []\n\n    def __init_subclass__(cls, **kwargs):\n        super().__init_subclass__(**kwargs)\n        PluginBase.registry.append(cls)\n        print(f\"registered plugin: {cls.__name__}\")\n\nclass CSVPlugin(PluginBase):\n    pass\n\nclass JSONPlugin(PluginBase):\n    pass\n# registered plugin: CSVPlugin\n# registered plugin: JSONPlugin\n\nprint(PluginBase.registry)   # [\u003Cclass 'CSVPlugin'>, \u003Cclass 'JSONPlugin'>]\n",[29,1996,1997,2006,2016,2020,2036,2051,2061,2082,2086,2100,2104,2108,2121,2125,2130,2135,2139],{"__ignoreMap":27},[32,1998,1999,2001,2004],{"class":34,"line":35},[32,2000,39],{"class":38},[32,2002,2003],{"class":42}," PluginBase",[32,2005,47],{"class":46},[32,2007,2008,2011,2013],{"class":34,"line":50},[32,2009,2010],{"class":46},"    registry ",[32,2012,214],{"class":38},[32,2014,2015],{"class":46}," []\n",[32,2017,2018],{"class":34,"line":62},[32,2019,76],{"emptyLinePlaceholder":75},[32,2021,2022,2024,2027,2030,2033],{"class":34,"line":72},[32,2023,53],{"class":38},[32,2025,2026],{"class":82}," __init_subclass__",[32,2028,2029],{"class":46},"(cls, ",[32,2031,2032],{"class":38},"**",[32,2034,2035],{"class":46},"kwargs):\n",[32,2037,2038,2040,2042,2044,2046,2048],{"class":34,"line":79},[32,2039,685],{"class":82},[32,2041,452],{"class":46},[32,2043,1988],{"class":82},[32,2045,86],{"class":46},[32,2047,2032],{"class":38},[32,2049,2050],{"class":46},"kwargs)\n",[32,2052,2053,2056,2059],{"class":34,"line":99},[32,2054,2055],{"class":46},"        PluginBase.registry.append(",[32,2057,2058],{"class":82},"cls",[32,2060,621],{"class":46},[32,2062,2063,2065,2067,2069,2072,2074,2076,2078,2080],{"class":34,"line":119},[32,2064,598],{"class":82},[32,2066,86],{"class":46},[32,2068,603],{"class":38},[32,2070,2071],{"class":68},"\"registered plugin: ",[32,2073,1023],{"class":82},[32,2075,1005],{"class":46},[32,2077,1028],{"class":82},[32,2079,618],{"class":68},[32,2081,621],{"class":46},[32,2083,2084],{"class":34,"line":139},[32,2085,76],{"emptyLinePlaceholder":75},[32,2087,2088,2090,2093,2095,2098],{"class":34,"line":275},[32,2089,39],{"class":38},[32,2091,2092],{"class":42}," CSVPlugin",[32,2094,86],{"class":46},[32,2096,2097],{"class":42},"PluginBase",[32,2099,377],{"class":46},[32,2101,2102],{"class":34,"line":281},[32,2103,730],{"class":38},[32,2105,2106],{"class":34,"line":299},[32,2107,76],{"emptyLinePlaceholder":75},[32,2109,2110,2112,2115,2117,2119],{"class":34,"line":338},[32,2111,39],{"class":38},[32,2113,2114],{"class":42}," JSONPlugin",[32,2116,86],{"class":46},[32,2118,2097],{"class":42},[32,2120,377],{"class":46},[32,2122,2123],{"class":34,"line":508},[32,2124,730],{"class":38},[32,2126,2127],{"class":34,"line":525},[32,2128,2129],{"class":95},"# registered plugin: CSVPlugin\n",[32,2131,2132],{"class":34,"line":738},[32,2133,2134],{"class":95},"# registered plugin: JSONPlugin\n",[32,2136,2137],{"class":34,"line":744},[32,2138,76],{"emptyLinePlaceholder":75},[32,2140,2141,2143,2146],{"class":34,"line":749},[32,2142,83],{"class":82},[32,2144,2145],{"class":46},"(PluginBase.registry)   ",[32,2147,2148],{"class":95},"# [\u003Cclass 'CSVPlugin'>, \u003Cclass 'JSONPlugin'>]\n",[159,2150,2151,2153,2154,2157,2158,2160,2161,2163],{},[29,2152,1988],{}," is called automatically on the parent class every time a ",[537,2155,2156],{},"subclass"," is defined — a huge fraction of \"I need a metaclass to auto-register subclasses\" use cases are better served by this single hook, introduced specifically to avoid reaching for a full metaclass in the common case. ",[533,2159,1153],{},": prefer ",[29,2162,1988],{}," over a custom metaclass whenever the goal is \"run code when a subclass is created\" — it's simpler, composes better with other base classes, and doesn't require understanding the metaclass machinery at all.",[14,2165,2167,2170],{"id":2166},"__set_name__-descriptors-learning-their-own-attribute-name",[29,2168,2169],{},"__set_name__"," — Descriptors Learning Their Own Attribute Name",[19,2172,2173],{"language":21},[23,2174,2176],{"className":25,"code":2175,"language":21,"meta":27,"style":27},"class LoggedAttribute:\n    def __set_name__(self, owner, name):\n        self.name = name              # called automatically at class-creation time — knows its own name!\n        self.private_name = f\"_{name}\"\n\n    def __get__(self, instance, owner):\n        return getattr(instance, self.private_name, None)\n\n    def __set__(self, instance, value):\n        print(f\"setting {self.name} = {value!r}\")\n        setattr(instance, self.private_name, value)\n\nclass Account:\n    balance = LoggedAttribute()   # __set_name__ fires here, at class body execution, with name=\"balance\"\n\na = Account()\na.balance = 100   # setting balance = 100\nprint(a.balance)    # 100\n",[29,2177,2178,2187,2197,2212,2234,2238,2248,2267,2271,2281,2315,2326,2330,2339,2352,2356,2366,2379],{"__ignoreMap":27},[32,2179,2180,2182,2185],{"class":34,"line":35},[32,2181,39],{"class":38},[32,2183,2184],{"class":42}," LoggedAttribute",[32,2186,47],{"class":46},[32,2188,2189,2191,2194],{"class":34,"line":50},[32,2190,53],{"class":38},[32,2192,2193],{"class":82}," __set_name__",[32,2195,2196],{"class":46},"(self, owner, name):\n",[32,2198,2199,2201,2204,2206,2209],{"class":34,"line":62},[32,2200,1093],{"class":82},[32,2202,2203],{"class":46},".name ",[32,2205,214],{"class":38},[32,2207,2208],{"class":46}," name              ",[32,2210,2211],{"class":95},"# called automatically at class-creation time — knows its own name!\n",[32,2213,2214,2216,2219,2221,2223,2226,2228,2230,2232],{"class":34,"line":72},[32,2215,1093],{"class":82},[32,2217,2218],{"class":46},".private_name ",[32,2220,214],{"class":38},[32,2222,1018],{"class":38},[32,2224,2225],{"class":68},"\"_",[32,2227,609],{"class":82},[32,2229,612],{"class":46},[32,2231,615],{"class":82},[32,2233,1526],{"class":68},[32,2235,2236],{"class":34,"line":79},[32,2237,76],{"emptyLinePlaceholder":75},[32,2239,2240,2242,2245],{"class":34,"line":99},[32,2241,53],{"class":38},[32,2243,2244],{"class":82}," __get__",[32,2246,2247],{"class":46},"(self, instance, owner):\n",[32,2249,2250,2252,2254,2257,2259,2262,2265],{"class":34,"line":119},[32,2251,65],{"class":38},[32,2253,1686],{"class":82},[32,2255,2256],{"class":46},"(instance, ",[32,2258,1099],{"class":82},[32,2260,2261],{"class":46},".private_name, ",[32,2263,2264],{"class":82},"None",[32,2266,621],{"class":46},[32,2268,2269],{"class":34,"line":139},[32,2270,76],{"emptyLinePlaceholder":75},[32,2272,2273,2275,2278],{"class":34,"line":275},[32,2274,53],{"class":38},[32,2276,2277],{"class":82}," __set__",[32,2279,2280],{"class":46},"(self, instance, value):\n",[32,2282,2283,2285,2287,2289,2292,2294,2297,2299,2302,2304,2307,2309,2311,2313],{"class":34,"line":281},[32,2284,598],{"class":82},[32,2286,86],{"class":46},[32,2288,603],{"class":38},[32,2290,2291],{"class":68},"\"setting ",[32,2293,1718],{"class":82},[32,2295,2296],{"class":46},".name",[32,2298,615],{"class":82},[32,2300,2301],{"class":68}," = ",[32,2303,609],{"class":82},[32,2305,2306],{"class":46},"value",[32,2308,987],{"class":38},[32,2310,615],{"class":82},[32,2312,618],{"class":68},[32,2314,621],{"class":46},[32,2316,2317,2319,2321,2323],{"class":34,"line":299},[32,2318,1531],{"class":82},[32,2320,2256],{"class":46},[32,2322,1099],{"class":82},[32,2324,2325],{"class":46},".private_name, value)\n",[32,2327,2328],{"class":34,"line":338},[32,2329,76],{"emptyLinePlaceholder":75},[32,2331,2332,2334,2337],{"class":34,"line":508},[32,2333,39],{"class":38},[32,2335,2336],{"class":42}," Account",[32,2338,47],{"class":46},[32,2340,2341,2344,2346,2349],{"class":34,"line":525},[32,2342,2343],{"class":46},"    balance ",[32,2345,214],{"class":38},[32,2347,2348],{"class":46}," LoggedAttribute()   ",[32,2350,2351],{"class":95},"# __set_name__ fires here, at class body execution, with name=\"balance\"\n",[32,2353,2354],{"class":34,"line":738},[32,2355,76],{"emptyLinePlaceholder":75},[32,2357,2358,2361,2363],{"class":34,"line":744},[32,2359,2360],{"class":46},"a ",[32,2362,214],{"class":38},[32,2364,2365],{"class":46}," Account()\n",[32,2367,2368,2371,2373,2376],{"class":34,"line":749},[32,2369,2370],{"class":46},"a.balance ",[32,2372,214],{"class":38},[32,2374,2375],{"class":82}," 100",[32,2377,2378],{"class":95},"   # setting balance = 100\n",[32,2380,2382,2384,2387],{"class":34,"line":2381},18,[32,2383,83],{"class":82},[32,2385,2386],{"class":46},"(a.balance)    ",[32,2388,2389],{"class":95},"# 100\n",[159,2391,2392,2393,2395],{},"This solves the pre-3.6 descriptor annoyance of manually repeating the attribute name as a string argument (chapter 15) — ",[29,2394,2169],{}," is called automatically by the class machinery once, at class creation, telling the descriptor instance exactly which name it was assigned to.",[14,2397,2399],{"id":2398},"tips-tricks","💡 Tips & Tricks",[2401,2402,2403,2413,2434,2458,2468],"ul",{},[2404,2405,2406,2409,2410,2412],"li",{},[533,2407,2408],{},"Idiom",": reach for ",[29,2411,1988],{}," before a metaclass for auto-registration or subclass validation — it solves the majority of real-world \"I need to hook class creation\" needs with far less complexity and better composability with multiple inheritance.",[2404,2414,2415,2418,2419,2422,2423,2425,2426,2429,2430,2433],{},[533,2416,2417],{},"Debug",": if a class's behavior seems to come from nowhere, check ",[29,2420,2421],{},"type(cls)"," — an unexpected non-",[29,2424,89],{}," result means a metaclass is involved, and ",[29,2427,2428],{},"cls.__mro__"," combined with ",[29,2431,2432],{},"type(cls).__mro__"," shows both the class's own inheritance and its metaclass's.",[2404,2435,2436,2438,2439,771,2442,2444,2445,771,2447,2449,2450,2453,2454,2457],{},[533,2437,2408],{},": always delegate to ",[29,2440,2441],{},"object.__setattr__",[29,2443,1981],{}," inside a custom ",[29,2446,1262],{},[29,2448,1265],{}," override for the \"real\" storage\u002Flookup — calling ",[29,2451,2452],{},"self.x = value"," or ",[29,2455,2456],{},"self.x"," directly inside these methods causes infinite recursion, since it calls the very method you're inside.",[2404,2459,2460,2463,2464,2467],{},[533,2461,2462],{},"Safety",": class decorators are strictly more composable than metaclasses — you can stack ",[29,2465,2466],{},"@dataclass @add_repr @register_plugin"," in any order, but a class can only ever have one metaclass (or a single metaclass that's a subclass of every ancestor's metaclass), making metaclasses a poor choice when a base class might already have one.",[2404,2469,2470,320,2472,2475,2476,2479],{},[533,2471,2417],{},[29,2473,2474],{},"vars(SomeClass)"," (equivalent to ",[29,2477,2478],{},"SomeClass.__dict__",") shows exactly what's defined directly on a class, distinct from what it inherited — useful for confirming whether a metaclass or class decorator actually added the attribute you expect at the level you expect.",[14,2481,2483],{"id":2482},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2401,2485,2486,2498,2527,2538,2559],{},[2404,2487,2488,2493,2494,2497],{},[533,2489,2490,2492],{},[29,2491,1259],{}," is only called when normal lookup fails — it will NOT fire again for an attribute that was actually stored on the instance",", which is why lazy-attribute patterns must call ",[29,2495,2496],{},"setattr(self, name, value)"," (not store into a separate cache dict) if the intent is \"compute once, then behave like a normal attribute.\"",[2404,2499,2500,2513,2514,2516,2517,2519,2520,2522,2523,2526],{},[533,2501,2502,2503,2453,2505,2507,2508,771,2510,2512],{},"Overriding ",[29,2504,1262],{},[29,2506,1265],{}," without delegating to ",[29,2509,2441],{},[29,2511,1981],{}," for real storage causes infinite recursion"," — ",[29,2515,2452],{}," inside your own ",[29,2518,1262],{}," calls ",[29,2521,1262],{}," again, and Python eventually raises ",[29,2524,2525],{},"RecursionError"," rather than looping forever.",[2404,2528,2529,2537],{},[533,2530,2531,2532,771,2534,2536],{},"A metaclass's ",[29,2533,455],{},[29,2535,564],{}," runs once, at class-definition time (usually at import), not per-instance"," — code inside them executes long before any instance of the class is ever created, which surprises developers expecting instance-creation semantics.",[2404,2539,2540,2543,2544,2547,2548,771,2551,2554,2555,2558],{},[533,2541,2542],{},"Two unrelated metaclasses generally cannot both be applied to the same class"," — Python requires a class's metaclass to be a subclass of all its bases' metaclasses, so combining ",[29,2545,2546],{},"class Foo(Base1, Base2)"," where ",[29,2549,2550],{},"Base1",[29,2552,2553],{},"Base2"," have unrelated metaclasses raises ",[29,2556,2557],{},"TypeError: metaclass conflict"," at class-definition time.",[2404,2560,2561,2513,2567,2570,2571,2574],{},[533,2562,2563,2566],{},[29,2564,2565],{},"type(\"Name\", bases, namespace)"," (dynamic class creation) silently accepts any string as the class name, including ones that aren't valid Python identifiers",[29,2568,2569],{},"type(\"123-invalid!\", (), {})"," creates a working class object whose ",[29,2572,2573],{},"__name__"," can't be referenced as a normal identifier anywhere in source code, only through the object reference itself.",[14,2576,2578],{"id":2577},"spot-the-bug","🧠 Spot the Bug",[159,2580,2581],{},"A caching descriptor is meant to compute an expensive value once per instance and reuse it afterward. It works for one instance but returns the wrong cached value when used across multiple instances of the same class. Find the bug.",[19,2583,2584],{"language":21},[23,2585,2587],{"className":25,"code":2586,"language":21,"meta":27,"style":27},"class CachedProperty:\n    def __init__(self, func):\n        self.func = func\n        self.cache = {}\n\n    def __get__(self, instance, owner):\n        if instance is None:\n            return self\n        if id(instance) not in self.cache:\n            self.cache[id(instance)] = self.func(instance)\n        return self.cache[id(instance)]\n\nclass Report:\n    def __init__(self, data):\n        self.data = data\n\n    @CachedProperty\n    def total(self):\n        print(\"computing total...\")\n        return sum(self.data)\n\nr1 = Report([1, 2, 3])\nprint(r1.total)   # computing total... 6\ndel r1\nr2 = Report([10, 20])\nprint(r2.total)   # sometimes prints a STALE cached value instead of recomputing!\n",[29,2588,2589,2598,2607,2619,2630,2634,2642,2657,2665,2685,2706,2719,2723,2732,2741,2753,2757,2762,2771,2783,2798,2803,2828,2839,2848,2868],{"__ignoreMap":27},[32,2590,2591,2593,2596],{"class":34,"line":35},[32,2592,39],{"class":38},[32,2594,2595],{"class":42}," CachedProperty",[32,2597,47],{"class":46},[32,2599,2600,2602,2604],{"class":34,"line":50},[32,2601,53],{"class":38},[32,2603,656],{"class":82},[32,2605,2606],{"class":46},"(self, func):\n",[32,2608,2609,2611,2614,2616],{"class":34,"line":62},[32,2610,1093],{"class":82},[32,2612,2613],{"class":46},".func ",[32,2615,214],{"class":38},[32,2617,2618],{"class":46}," func\n",[32,2620,2621,2623,2626,2628],{"class":34,"line":72},[32,2622,1093],{"class":82},[32,2624,2625],{"class":46},".cache ",[32,2627,214],{"class":38},[32,2629,1301],{"class":46},[32,2631,2632],{"class":34,"line":79},[32,2633,76],{"emptyLinePlaceholder":75},[32,2635,2636,2638,2640],{"class":34,"line":99},[32,2637,53],{"class":38},[32,2639,2244],{"class":82},[32,2641,2247],{"class":46},[32,2643,2644,2646,2649,2652,2655],{"class":34,"line":119},[32,2645,825],{"class":38},[32,2647,2648],{"class":46}," instance ",[32,2650,2651],{"class":38},"is",[32,2653,2654],{"class":82}," None",[32,2656,47],{"class":46},[32,2658,2659,2662],{"class":34,"line":139},[32,2660,2661],{"class":38},"            return",[32,2663,2664],{"class":82}," self\n",[32,2666,2667,2669,2672,2675,2678,2680,2682],{"class":34,"line":275},[32,2668,825],{"class":38},[32,2670,2671],{"class":82}," id",[32,2673,2674],{"class":46},"(instance) ",[32,2676,2677],{"class":38},"not",[32,2679,839],{"class":38},[32,2681,1002],{"class":82},[32,2683,2684],{"class":46},".cache:\n",[32,2686,2687,2690,2693,2696,2699,2701,2703],{"class":34,"line":281},[32,2688,2689],{"class":82},"            self",[32,2691,2692],{"class":46},".cache[",[32,2694,2695],{"class":82},"id",[32,2697,2698],{"class":46},"(instance)] ",[32,2700,214],{"class":38},[32,2702,1002],{"class":82},[32,2704,2705],{"class":46},".func(instance)\n",[32,2707,2708,2710,2712,2714,2716],{"class":34,"line":299},[32,2709,65],{"class":38},[32,2711,1002],{"class":82},[32,2713,2692],{"class":46},[32,2715,2695],{"class":82},[32,2717,2718],{"class":46},"(instance)]\n",[32,2720,2721],{"class":34,"line":338},[32,2722,76],{"emptyLinePlaceholder":75},[32,2724,2725,2727,2730],{"class":34,"line":508},[32,2726,39],{"class":38},[32,2728,2729],{"class":42}," Report",[32,2731,47],{"class":46},[32,2733,2734,2736,2738],{"class":34,"line":525},[32,2735,53],{"class":38},[32,2737,656],{"class":82},[32,2739,2740],{"class":46},"(self, data):\n",[32,2742,2743,2745,2748,2750],{"class":34,"line":738},[32,2744,1093],{"class":82},[32,2746,2747],{"class":46},".data ",[32,2749,214],{"class":38},[32,2751,2752],{"class":46}," data\n",[32,2754,2755],{"class":34,"line":744},[32,2756,76],{"emptyLinePlaceholder":75},[32,2758,2759],{"class":34,"line":749},[32,2760,2761],{"class":42},"    @CachedProperty\n",[32,2763,2764,2766,2769],{"class":34,"line":2381},[32,2765,53],{"class":38},[32,2767,2768],{"class":42}," total",[32,2770,59],{"class":46},[32,2772,2774,2776,2778,2781],{"class":34,"line":2773},19,[32,2775,598],{"class":82},[32,2777,86],{"class":46},[32,2779,2780],{"class":68},"\"computing total...\"",[32,2782,621],{"class":46},[32,2784,2786,2788,2791,2793,2795],{"class":34,"line":2785},20,[32,2787,65],{"class":38},[32,2789,2790],{"class":82}," sum",[32,2792,86],{"class":46},[32,2794,1099],{"class":82},[32,2796,2797],{"class":46},".data)\n",[32,2799,2801],{"class":34,"line":2800},21,[32,2802,76],{"emptyLinePlaceholder":75},[32,2804,2806,2809,2811,2814,2816,2818,2820,2822,2825],{"class":34,"line":2805},22,[32,2807,2808],{"class":46},"r1 ",[32,2810,214],{"class":38},[32,2812,2813],{"class":46}," Report([",[32,2815,1124],{"class":82},[32,2817,172],{"class":46},[32,2819,1129],{"class":82},[32,2821,172],{"class":46},[32,2823,2824],{"class":82},"3",[32,2826,2827],{"class":46},"])\n",[32,2829,2831,2833,2836],{"class":34,"line":2830},23,[32,2832,83],{"class":82},[32,2834,2835],{"class":46},"(r1.total)   ",[32,2837,2838],{"class":95},"# computing total... 6\n",[32,2840,2842,2845],{"class":34,"line":2841},24,[32,2843,2844],{"class":38},"del",[32,2846,2847],{"class":46}," r1\n",[32,2849,2851,2854,2856,2858,2861,2863,2866],{"class":34,"line":2850},25,[32,2852,2853],{"class":46},"r2 ",[32,2855,214],{"class":38},[32,2857,2813],{"class":46},[32,2859,2860],{"class":82},"10",[32,2862,172],{"class":46},[32,2864,2865],{"class":82},"20",[32,2867,2827],{"class":46},[32,2869,2871,2873,2876],{"class":34,"line":2870},26,[32,2872,83],{"class":82},[32,2874,2875],{"class":46},"(r2.total)   ",[32,2877,2878],{"class":95},"# sometimes prints a STALE cached value instead of recomputing!\n",[2880,2881,2882,2886,2919,2934,3070,3080],"details",{},[2883,2884,2885],"summary",{},"Answer",[159,2887,2888,2889,2892,2893,2896,2897,2900,2901,2904,2905,2908,2909,2911,2912,2915,2916,2918],{},"The cache key is ",[29,2890,2891],{},"id(instance)"," — the instance's memory address. CPython is free to reuse a memory address once an object's reference count drops to zero and it's garbage collected, which is exactly what can happen to ",[29,2894,2895],{},"r1"," after ",[29,2898,2899],{},"del r1",": if ",[29,2902,2903],{},"r2"," happens to be allocated at the same now-freed address, ",[29,2906,2907],{},"id(r2) == id(r1)","'s old value, and the descriptor's cache serves ",[29,2910,2895],{},"'s stale computed ",[29,2913,2914],{},"total"," for ",[29,2917,2903],{}," instead of computing a fresh one — a correctness bug that depends on non-deterministic memory reuse timing, making it intermittent and hard to reproduce.",[159,2920,2921,2922,2925,2926,2929,2930,2933],{},"The fix is to store the cache on the ",[537,2923,2924],{},"instance itself"," (as ",[29,2927,2928],{},"functools.cached_property",", chapter 15's descriptor material, and the standard library all do), not in a dict keyed by a potentially-recycled ",[29,2931,2932],{},"id()",":",[19,2935,2936],{"language":21},[23,2937,2939],{"className":25,"code":2938,"language":21,"meta":27,"style":27},"class CachedProperty:\n    def __init__(self, func):\n        self.func = func\n        self.attr_name = None\n\n    def __set_name__(self, owner, name):\n        self.attr_name = name\n\n    def __get__(self, instance, owner):\n        if instance is None:\n            return self\n        value = self.func(instance)\n        instance.__dict__[self.attr_name] = value   # shadows the descriptor on FUTURE lookups for this instance\n        return value\n",[29,2940,2941,2949,2957,2967,2979,2983,2991,3002,3006,3014,3026,3032,3042,3064],{"__ignoreMap":27},[32,2942,2943,2945,2947],{"class":34,"line":35},[32,2944,39],{"class":38},[32,2946,2595],{"class":42},[32,2948,47],{"class":46},[32,2950,2951,2953,2955],{"class":34,"line":50},[32,2952,53],{"class":38},[32,2954,656],{"class":82},[32,2956,2606],{"class":46},[32,2958,2959,2961,2963,2965],{"class":34,"line":62},[32,2960,1093],{"class":82},[32,2962,2613],{"class":46},[32,2964,214],{"class":38},[32,2966,2618],{"class":46},[32,2968,2969,2971,2974,2976],{"class":34,"line":72},[32,2970,1093],{"class":82},[32,2972,2973],{"class":46},".attr_name ",[32,2975,214],{"class":38},[32,2977,2978],{"class":82}," None\n",[32,2980,2981],{"class":34,"line":79},[32,2982,76],{"emptyLinePlaceholder":75},[32,2984,2985,2987,2989],{"class":34,"line":99},[32,2986,53],{"class":38},[32,2988,2193],{"class":82},[32,2990,2196],{"class":46},[32,2992,2993,2995,2997,2999],{"class":34,"line":119},[32,2994,1093],{"class":82},[32,2996,2973],{"class":46},[32,2998,214],{"class":38},[32,3000,3001],{"class":46}," name\n",[32,3003,3004],{"class":34,"line":139},[32,3005,76],{"emptyLinePlaceholder":75},[32,3007,3008,3010,3012],{"class":34,"line":275},[32,3009,53],{"class":38},[32,3011,2244],{"class":82},[32,3013,2247],{"class":46},[32,3015,3016,3018,3020,3022,3024],{"class":34,"line":281},[32,3017,825],{"class":38},[32,3019,2648],{"class":46},[32,3021,2651],{"class":38},[32,3023,2654],{"class":82},[32,3025,47],{"class":46},[32,3027,3028,3030],{"class":34,"line":299},[32,3029,2661],{"class":38},[32,3031,2664],{"class":82},[32,3033,3034,3036,3038,3040],{"class":34,"line":338},[32,3035,1347],{"class":46},[32,3037,214],{"class":38},[32,3039,1002],{"class":82},[32,3041,2705],{"class":46},[32,3043,3044,3047,3049,3051,3053,3056,3058,3061],{"class":34,"line":508},[32,3045,3046],{"class":46},"        instance.",[32,3048,1008],{"class":82},[32,3050,1620],{"class":46},[32,3052,1099],{"class":82},[32,3054,3055],{"class":46},".attr_name] ",[32,3057,214],{"class":38},[32,3059,3060],{"class":46}," value   ",[32,3062,3063],{"class":95},"# shadows the descriptor on FUTURE lookups for this instance\n",[32,3065,3066,3068],{"class":34,"line":525},[32,3067,65],{"class":38},[32,3069,1377],{"class":46},[159,3071,3072,3073,3076,3077,3079],{},"Storing the cached value directly in ",[29,3074,3075],{},"instance.__dict__"," under the same name ties its lifetime to the instance itself — no risk of ",[29,3078,2932],{}," collisions, and it disappears automatically when the instance is garbage collected, rather than leaking forever in a class-level dict.",[159,3081,3082,3085,3086,3089,3090,3092,3093,3096],{},[533,3083,3084],{},"The lesson",": never use ",[29,3087,3088],{},"id(obj)"," as a long-lived dictionary key intended to uniquely identify an object across its lifetime — ",[29,3091,2932],{}," is only guaranteed unique among objects that are alive ",[537,3094,3095],{},"simultaneously","; once an object is freed, its id can be recycled by an entirely unrelated object.",[14,3098,3100],{"id":3099},"key-takeaways","Key Takeaways",[2401,3102,3103,3115,3123,3129,3144,3149],{},[2404,3104,3105,3106,2513,3108,3111,3112,3114],{},"Classes are ordinary objects whose type is (by default) ",[29,3107,89],{},[29,3109,3110],{},"type(name, bases, namespace)"," builds a class dynamically, exactly mirroring what a ",[29,3113,39],{}," statement does under the hood.",[2404,3116,3117,3118,771,3120,3122],{},"A metaclass is \"the class of a class,\" controlling how classes using it are constructed; its ",[29,3119,455],{},[29,3121,564],{}," run once, at class-definition time, not per-instance.",[2404,3124,3125,3126,3128],{},"Class decorators solve most \"customize this class\" needs more simply than metaclasses and compose far better — reach for ",[29,3127,1988],{}," or a class decorator before writing a custom metaclass.",[2404,3130,3131,3133,3134,3136,3137,3139,3140,771,3142,1982],{},[29,3132,1259],{}," fires only on failed lookups (a fallback for missing attributes); ",[29,3135,1265],{}," fires on every access; ",[29,3138,1262],{}," fires on every assignment — both of the latter two must delegate to ",[29,3141,2441],{},[29,3143,1265],{},[2404,3145,3146,3148],{},[29,3147,2169],{}," lets a descriptor learn the attribute name it was assigned to automatically at class-creation time, removing the need to pass the name manually.",[2404,3150,3151,3152,3154],{},"Never key a long-lived cache by ",[29,3153,3088],{}," — once an object is garbage collected, its memory address can be reused by an unrelated object, causing stale or cross-contaminated cache hits.",[3156,3157,3158],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":27,"searchDepth":50,"depth":50,"links":3160},[3161,3162,3167,3168,3175,3177,3179,3180,3181,3182],{"id":16,"depth":50,"text":17},{"id":355,"depth":50,"text":356,"children":3163},[3164,3166],{"id":558,"depth":62,"text":3165},"__new__ vs __init__ on a Metaclass",{"id":777,"depth":62,"text":778},{"id":926,"depth":50,"text":927},{"id":1255,"depth":50,"text":3169,"children":3170},"Dynamic Attribute Access: __getattr__, __setattr__, __getattribute__",[3171,3173],{"id":1582,"depth":62,"text":3172},"__setattr__ — intercepting every attribute assignment",{"id":1820,"depth":62,"text":3174},"__getattribute__ — intercepting every attribute read, found or not",{"id":1985,"depth":50,"text":3176},"__init_subclass__ — A Lightweight Alternative to Metaclasses",{"id":2166,"depth":50,"text":3178},"__set_name__ — Descriptors Learning Their Own Attribute Name",{"id":2398,"depth":50,"text":2399},{"id":2482,"depth":50,"text":2483},{"id":2577,"depth":50,"text":2578},{"id":3099,"depth":50,"text":3100},"md",{},"\u002Fpython\u002F23-metaprogramming",{"title":5,"description":27},"python\u002F23-metaprogramming","pqKciat7MTNMKV4Qaew2bFmGHkoN54HjB4_q1P4pEaU",1789924651797]