Python Metaclasses - Real Python

文章推薦指數: 80 %
投票人數:10人

type is a metaclass, of which classes are instances. Just as an ordinary object is an instance of a class, any new-style class in Python, and thus any class in ... Start Here LearnPython PythonTutorials→In-deptharticlesandvideocourses LearningPaths→Guidedstudyplansforacceleratedlearning Quizzes→Checkyourlearningprogress BrowseTopics→Focusonaspecificareaorskilllevel CommunityChat→LearnwithotherPythonistas OfficeHours→LiveQ&AcallswithPythonexperts Podcast→Hearwhat’snewintheworldofPython Books→Roundoutyourknowledgeandlearnoffline UnlockAllContent→ More PythonLearningResources PythonNewsletter PythonJobBoard MeettheTeam BecomeaTutorialAuthor BecomeaVideoInstructor Search Join Sign‑In PythonMetaclasses byJohnSturtz advanced python MarkasCompleted Tweet Share Email TableofContents Old-Stylevs.New-StyleClasses Old-StyleClasses New-StyleClasses TypeandClass DefiningaClassDynamically Example1 Example2 Example3 Example4 CustomMetaclasses IsThisReallyNecessary? Conclusion Removeads Thetermmetaprogrammingreferstothepotentialforaprogramtohaveknowledgeoformanipulateitself.Pythonsupportsaformofmetaprogrammingforclassescalledmetaclasses. MetaclassesareanesotericOOPconcept,lurkingbehindvirtuallyallPythoncode.Youareusingthemwhetheryouareawareofitornot.Forthemostpart,youdon’tneedtobeawareofit.MostPythonprogrammersrarely,ifever,havetothinkaboutmetaclasses. Whentheneedarises,however,Pythonprovidesacapabilitythatnotallobject-orientedlanguagessupport:youcangetunderthehoodanddefinecustommetaclasses.Theuseofcustommetaclassesissomewhatcontroversial,assuggestedbythefollowingquotefromTimPeters,thePythonguruwhoauthoredtheZenofPython: “Metaclassesaredeepermagicthan99%ofusersshouldeverworryabout.Ifyouwonderwhetheryouneedthem,youdon’t(thepeoplewhoactuallyneedthemknowwithcertaintythattheyneedthem,anddon’tneedanexplanationaboutwhy).” —TimPeters TherearePythonistas(asPythonaficionadosareknown)whobelievethatyoushouldneverusecustommetaclasses.Thatmightbegoingabitfar,butitisprobablytruethatcustommetaclassesmostlyaren’tnecessary.Ifitisn’tprettyobviousthataproblemcallsforthem,thenitwillprobablybecleanerandmorereadableifsolvedinasimplerway. Still,understandingPythonmetaclassesisworthwhile,becauseitleadstoabetterunderstandingoftheinternalsofPythonclassesingeneral.Youneverknow:youmayonedayfindyourselfinoneofthosesituationswhereyoujustknowthatacustommetaclassiswhatyouwant. GetNotified:Don’tmissthefollowuptothistutorial—ClickheretojointheRealPythonNewsletterandyou’llknowwhenthenextinstallmentcomesout. Old-Stylevs.New-StyleClasses InthePythonrealm,aclasscanbeoneoftwovarieties.Noofficialterminologyhasbeendecidedon,sotheyareinformallyreferredtoasold-styleandnew-styleclasses. RemoveadsOld-StyleClasses Withold-styleclasses,classandtypearenotquitethesamething.Aninstanceofanold-styleclassisalwaysimplementedfromasinglebuilt-intypecalledinstance.Ifobjisaninstanceofanold-styleclass,obj.__class__designatestheclass,buttype(obj)isalwaysinstance.ThefollowingexampleistakenfromPython2.7: >>>>>>classFoo: ...pass ... >>>x=Foo() >>>x.__class__ >>>type(x) New-StyleClasses New-styleclassesunifytheconceptsofclassandtype.Ifobjisaninstanceofanew-styleclass,type(obj)isthesameasobj.__class__: >>>>>>classFoo: ...pass >>>obj=Foo() >>>obj.__class__ >>>type(obj) >>>obj.__class__istype(obj) True >>>>>>n=5 >>>d={'x':1,'y':2} >>>classFoo: ...pass ... >>>x=Foo() >>>forobjin(n,d,x): ...print(type(obj)isobj.__class__) ... True True True TypeandClass InPython3,allclassesarenew-styleclasses.Thus,inPython3itisreasonabletorefertoanobject’stypeanditsclassinterchangeably. Note:InPython2,classesareold-stylebydefault.PriortoPython2.2,new-styleclassesweren’tsupportedatall.FromPython2.2onward,theycanbecreatedbutmustbeexplicitlydeclaredasnew-style. Rememberthat,inPython,everythingisanobject.Classesareobjectsaswell.Asaresult,aclassmusthaveatype.Whatisthetypeofaclass? Considerthefollowing: >>>>>>classFoo: ...pass ... >>>x=Foo() >>>type(x) >>>type(Foo) ThetypeofxisclassFoo,asyouwouldexpect.ButthetypeofFoo,theclassitself,istype.Ingeneral,thetypeofanynew-styleclassistype. Thetypeofthebuilt-inclassesyouarefamiliarwithisalsotype: >>>>>>fortinint,float,dict,list,tuple: ...print(type(t)) ... Forthatmatter,thetypeoftypeistypeaswell(yes,really): >>>>>>type(type) typeisametaclass,ofwhichclassesareinstances.Justasanordinaryobjectisaninstanceofaclass,anynew-styleclassinPython,andthusanyclassinPython3,isaninstanceofthetypemetaclass. Intheabovecase: xisaninstanceofclassFoo. Fooisaninstanceofthetypemetaclass. typeisalsoaninstanceofthetypemetaclass,soitisaninstanceofitself. RemoveadsDefiningaClassDynamically Thebuilt-intype()function,whenpassedoneargument,returnsthetypeofanobject.Fornew-styleclasses,thatisgenerallythesameastheobject’s__class__attribute: >>>>>>type(3) >>>type(['foo','bar','baz']) >>>t=(1,2,3,4,5) >>>type(t) >>>classFoo: ...pass ... >>>type(Foo()) Youcanalsocalltype()withthreearguments—type(,,): specifiestheclassname.Thisbecomesthe__name__attributeoftheclass. specifiesatupleofthebaseclassesfromwhichtheclassinherits.Thisbecomesthe__bases__attributeoftheclass. specifiesanamespacedictionarycontainingdefinitionsfortheclassbody.Thisbecomesthe__dict__attributeoftheclass. Callingtype()inthismannercreatesanewinstanceofthetypemetaclass.Inotherwords,itdynamicallycreatesanewclass. Ineachofthefollowingexamples,thetopsnippetdefinesaclassdynamicallywithtype(),whilethesnippetbelowitdefinestheclasstheusualway,withtheclassstatement.Ineachcase,thetwosnippetsarefunctionallyequivalent. Example1 Inthisfirstexample,theandargumentspassedtotype()arebothempty.Noinheritancefromanyparentclassisspecified,andnothingisinitiallyplacedinthenamespacedictionary.Thisisthesimplestclassdefinitionpossible: >>>>>>Foo=type('Foo',(),{}) >>>x=Foo() >>>x <__main__.fooobjectat0x04cfad50> >>>>>>classFoo: ...pass ... >>>x=Foo() >>>x <__main__.fooobjectat0x0370ad50> Example2 Here,isatuplewithasingleelementFoo,specifyingtheparentclassthatBarinheritsfrom.Anattribute,attr,isinitiallyplacedintothenamespacedictionary: >>>>>>Bar=type('Bar',(Foo,),dict(attr=100)) >>>x=Bar() >>>x.attr 100 >>>x.__class__ >>>x.__class__.__bases__ (,) >>>>>>classBar(Foo): ...attr=100 ... >>>x=Bar() >>>x.attr 100 >>>x.__class__ >>>x.__class__.__bases__ (,) Example3 Thistime,isagainempty.Twoobjectsareplacedintothenamespacedictionaryviatheargument.Thefirstisanattributenamedattrandthesecondafunctionnamedattr_val,whichbecomesamethodofthedefinedclass: >>>>>>Foo=type( ...'Foo', ...(), ...{ ...'attr':100, ...'attr_val':lambdax:x.attr ...} ...) >>>x=Foo() >>>x.attr 100 >>>x.attr_val() 100 >>>>>>classFoo: ...attr=100 ...defattr_val(self): ...returnself.attr ... >>>x=Foo() >>>x.attr 100 >>>x.attr_val() 100 Example4 OnlyverysimplefunctionscanbedefinedwithlambdainPython.Inthefollowingexample,aslightlymorecomplexfunctionisdefinedexternallythenassignedtoattr_valinthenamespacedictionaryviathenamef: >>>>>>deff(obj): ...print('attr=',obj.attr) ... >>>Foo=type( ...'Foo', ...(), ...{ ...'attr':100, ...'attr_val':f ...} ...) >>>x=Foo() >>>x.attr 100 >>>x.attr_val() attr=100 >>>>>>deff(obj): ...print('attr=',obj.attr) ... >>>classFoo: ...attr=100 ...attr_val=f ... >>>x=Foo() >>>x.attr 100 >>>x.attr_val() attr=100 RemoveadsCustomMetaclasses Consideragainthiswell-wornexample: >>>>>>classFoo: ...pass ... >>>f=Foo() TheexpressionFoo()createsanewinstanceofclassFoo.WhentheinterpreterencountersFoo(),thefollowingoccurs: The__call__()methodofFoo’sparentclassiscalled.SinceFooisastandardnew-styleclass,itsparentclassisthetypemetaclass,sotype’s__call__()methodisinvoked. That__call__()methodinturninvokesthefollowing: __new__() __init__() IfFoodoesnotdefine__new__()and__init__(),defaultmethodsareinheritedfromFoo’sancestry.ButifFoodoesdefinethesemethods,theyoverridethosefromtheancestry,whichallowsforcustomizedbehaviorwheninstantiatingFoo. Inthefollowing,acustommethodcallednew()isdefinedandassignedasthe__new__()methodforFoo: >>>>>>defnew(cls): ...x=object.__new__(cls) ...x.attr=100 ...returnx ... >>>Foo.__new__=new >>>f=Foo() >>>f.attr 100 >>>g=Foo() >>>g.attr 100 ThismodifiestheinstantiationbehaviorofclassFoo:eachtimeaninstanceofFooiscreated,bydefaultitisinitializedwithanattributecalledattr,whichhasavalueof100.(Codelikethiswouldmoreusuallyappearinthe__init__()methodandnottypicallyin__new__().Thisexampleiscontrivedfordemonstrationpurposes.) Now,ashasalreadybeenreiterated,classesareobjectstoo.SupposeyouwantedtosimilarlycustomizeinstantiationbehaviorwhencreatingaclasslikeFoo.Ifyouweretofollowthepatternabove,you’dagaindefineacustommethodandassignitasthe__new__()methodfortheclassofwhichFooisaninstance.Fooisaninstanceofthetypemetaclass,sothecodelookssomethinglikethis: >>>#Spoileralert:Thisdoesn'twork! >>>defnew(cls): ...x=type.__new__(cls) ...x.attr=100 ...returnx ... >>>type.__new__=new Traceback(mostrecentcalllast): File"",line1,in type.__new__=new TypeError:can'tsetattributesofbuilt-in/extensiontype'type' Except,asyoucansee,youcan’treassignthe__new__()methodofthetypemetaclass.Pythondoesn’tallowit. Thisisprobablyjustaswell.typeisthemetaclassfromwhichallnew-styleclassesarederived.Youreallyshouldn’tbemuckingaroundwithitanyway.Butthenwhatrecourseisthere,ifyouwanttocustomizeinstantiationofaclass? Onepossiblesolutionisacustommetaclass.Essentially,insteadofmuckingaroundwiththetypemetaclass,youcandefineyourownmetaclass,whichderivesfromtype,andthenyoucanmuckaroundwiththatinstead. Thefirststepistodefineametaclassthatderivesfromtype,asfollows: >>>>>>classMeta(type): ...def__new__(cls,name,bases,dct): ...x=super().__new__(cls,name,bases,dct) ...x.attr=100 ...returnx ... ThedefinitionheaderclassMeta(type):specifiesthatMetaderivesfromtype.Sincetypeisametaclass,thatmakesMetaametaclassaswell. Notethatacustom__new__()methodhasbeendefinedforMeta.Itwasn’tpossibletodothattothetypemetaclassdirectly.The__new__()methoddoesthefollowing: Delegatesviasuper()tothe__new__()methodoftheparentmetaclass(type)toactuallycreateanewclass Assignsthecustomattributeattrtotheclass,withavalueof100 Returnsthenewlycreatedclass Nowtheotherhalfofthevoodoo:DefineanewclassFooandspecifythatitsmetaclassisthecustommetaclassMeta,ratherthanthestandardmetaclasstype.Thisisdoneusingthemetaclasskeywordintheclassdefinitionasfollows: >>>>>>classFoo(metaclass=Meta): ...pass ... >>>Foo.attr 100 Voila!FoohaspickeduptheattrattributeautomaticallyfromtheMetametaclass.Ofcourse,anyotherclassesyoudefinesimilarlywilldolikewise: >>>>>>classBar(metaclass=Meta): ...pass ... >>>classQux(metaclass=Meta): ...pass ... >>>Bar.attr,Qux.attr (100,100) Inthesamewaythataclassfunctionsasatemplateforthecreationofobjects,ametaclassfunctionsasatemplateforthecreationofclasses.Metaclassesaresometimesreferredtoasclassfactories. Comparethefollowingtwoexamples: ObjectFactory: >>>>>>classFoo: ...def__init__(self): ...self.attr=100 ... >>>x=Foo() >>>x.attr 100 >>>y=Foo() >>>y.attr 100 >>>z=Foo() >>>z.attr 100 ClassFactory: >>>>>>classMeta(type): ...def__init__( ...cls,name,bases,dct ...): ...cls.attr=100 ... >>>classX(metaclass=Meta): ...pass ... >>>X.attr 100 >>>classY(metaclass=Meta): ...pass ... >>>Y.attr 100 >>>classZ(metaclass=Meta): ...pass ... >>>Z.attr 100 RemoveadsIsThisReallyNecessary? Assimpleastheaboveclassfactoryexampleis,itistheessenceofhowmetaclasseswork.Theyallowcustomizationofclassinstantiation. Still,thisisalotoffussjusttobestowthecustomattributeattroneachnewlycreatedclass.Doyoureallyneedametaclassjustforthat? InPython,thereareatleastacoupleotherwaysinwhicheffectivelythesamethingcanbeaccomplished: SimpleInheritance: >>>>>>classBase: ...attr=100 ... >>>classX(Base): ...pass ... >>>classY(Base): ...pass ... >>>classZ(Base): ...pass ... >>>X.attr 100 >>>Y.attr 100 >>>Z.attr 100 ClassDecorator: >>>>>>defdecorator(cls): ...classNewClass(cls): ...attr=100 ...returnNewClass ... >>>@decorator ...classX: ...pass ... >>>@decorator ...classY: ...pass ... >>>@decorator ...classZ: ...pass ... >>>X.attr 100 >>>Y.attr 100 >>>Z.attr 100 Conclusion AsTimPeterssuggests,metaclassescaneasilyveerintotherealmofbeinga“solutioninsearchofaproblem.”Itisn’ttypicallynecessarytocreatecustommetaclasses.Iftheproblemathandcanbesolvedinasimplerway,itprobablyshouldbe.Still,itisbeneficialtounderstandmetaclassessothatyouunderstandPythonclassesingeneralandcanrecognizewhenametaclassreallyistheappropriatetooltouse. MarkasCompleted 🐍PythonTricks💌 Getashort&sweetPythonTrickdeliveredtoyourinboxeverycoupleofdays.Nospamever.Unsubscribeanytime.CuratedbytheRealPythonteam. SendMePythonTricks» AboutJohnSturtz JohnisanavidPythonistaandamemberoftheRealPythontutorialteam. »MoreaboutJohn EachtutorialatRealPythoniscreatedbyateamofdeveloperssothatitmeetsourhighqualitystandards.Theteammemberswhoworkedonthistutorialare: Aldren Dan Joanna MasterReal-WorldPythonSkillsWithUnlimitedAccesstoReal Python Joinusandgetaccesstothousandsoftutorials,hands-onvideocourses,andacommunityofexpert Pythonistas: LevelUpYourPythonSkills» MasterReal-WorldPythonSkillsWithUnlimitedAccesstoReal Python Joinusandgetaccesstothousandsoftutorials,hands-onvideocourses,andacommunityofexpertPythonistas: LevelUpYourPythonSkills» WhatDoYouThink? Ratethisarticle: Tweet Share Share Email What’syour#1takeawayorfavoritethingyoulearned?Howareyougoingtoputyournewfoundskillstouse?Leaveacommentbelowandletusknow. CommentingTips:Themostusefulcommentsarethosewrittenwiththegoaloflearningfromorhelpingoutotherstudents.Gettipsforaskinggoodquestionsandgetanswerstocommonquestionsinoursupportportal.Lookingforareal-timeconversation?VisittheRealPythonCommunityChatorjointhenext“Office Hours”LiveQ&ASession.HappyPythoning! KeepLearning RelatedTutorialCategories: advanced python KeepreadingReal Pythonbycreatingafreeaccountorsigning in: Continue» Alreadyhaveanaccount?Sign-In —FREEEmailSeries— 🐍PythonTricks💌 GetPythonTricks» 🔒Nospam.Unsubscribeanytime. AllTutorialTopics advanced api basics best-practices community databases data-science devops django docker flask front-end gamedev gui intermediate machine-learning projects python testing tools web-dev web-scraping TableofContents Old-Stylevs.New-StyleClasses Old-StyleClasses New-StyleClasses TypeandClass DefiningaClassDynamically Example1 Example2 Example3 Example4 CustomMetaclasses IsThisReallyNecessary? Conclusion MarkasCompleted Tweet Share Email Almostthere!Completethisformandclickthebuttonbelowtogaininstantaccess: × JointheRealPythonCommunityNewsletter(MoreThan45,468PythonDevelopersHaveSubscribed) GetTheNewsletter» 🔒Nospam.Wetakeyourprivacyseriously.



請為這篇文章評分?