Primer on Python Decorators

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

In this introductory tutorial, we'll look at what Python decorators are and how to create and use them. Start Here LearnPython PythonTutorials→In-deptharticlesandtutorials VideoCourses→Step-by-stepvideolessons Quizzes→Checkyourlearningprogress LearningPaths→Guidedstudyplansforacceleratedlearning Community→LearnwithotherPythonistas Topics→Focusonaspecificareaorskilllevel UnlockAllContent Store RPMembership PythonBasicsBook PythonTricksBook CPythonInternalsBook TheRealPythonCourse ManagingPythonDependencies SublimeText+PythonSetup PythonicWallpapersPack PythonMugs,T-Shirts,andMore PythonistaCafeCommunity BrowseAll» More PythonNewsletter PythonPodcast PythonJobBoard MeettheTeam BecomeaTutorialAuthor BecomeaVideoInstructor Search Join Sign‑In PrimeronPythonDecorators byGeirArneHjelle intermediate python MarkasCompleted Tweet Share Email TableofContents Functions First-ClassObjects InnerFunctions ReturningFunctionsFromFunctions SimpleDecorators SyntacticSugar! ReusingDecorators DecoratingFunctionsWithArguments ReturningValuesFromDecoratedFunctions WhoAreYou,Really? AFewRealWorldExamples TimingFunctions DebuggingCode SlowingDownCode RegisteringPlugins IstheUserLoggedIn? FancyDecorators DecoratingClasses NestingDecorators DecoratorsWithArguments BothPlease,ButNeverMindtheBread StatefulDecorators ClassesasDecorators MoreRealWorldExamples SlowingDownCode,Revisited CreatingSingletons CachingReturnValues AddingInformationAboutUnits ValidatingJSON Conclusion FurtherReading Removeads WatchNowThistutorialhasarelatedvideocoursecreatedbytheRealPythonteam.Watchittogetherwiththewrittentutorialtodeepenyourunderstanding:PythonDecorators101 Inthistutorialondecorators,we’lllookatwhattheyareandhowtocreateandusethem.Decoratorsprovideasimplesyntaxforcallinghigher-orderfunctions. Bydefinition,adecoratorisafunctionthattakesanotherfunctionandextendsthebehaviorofthelatterfunctionwithoutexplicitlymodifyingit. Thissoundsconfusing,butit’sreallynot,especiallyafteryou’veseenafewexamplesofhowdecoratorswork.Youcanfindalltheexamplesfromthisarticlehere. FreeBonus:Clickheretogetaccesstoafree"ThePowerofPythonDecorators"guidethatshowsyouthreeadvanceddecoratorpatternsandtechniquesyoucanusetowritecleanerandmorePythonicprograms. DecoratorsCheatSheet:Clickheretogetaccesstoafreethree-pagePythondecoratorscheatsheetthatsummarizesthetechniquesexplainedinthistutorial. DecoratorsQ&ATranscript:Clickheretogetaccesstoa25-pagechatlogfromourPythondecoratorsQ&AsessionintheRealPythonCommunitySlackwherewediscussedcommondecoratorquestions. Updates: 08/22/2018:Majorupdateaddingmoreexamplesandmoreadvanceddecorators 01/12/2016:UpdatedexamplestoPython3(v3.5.1)syntaxandaddedanewexample 11/01/2015:Addedabriefexplanationonthefunctools.wraps()decorator Functions Beforeyoucanunderstanddecorators,youmustfirstunderstandhowfunctionswork.Forourpurposes,afunctionreturnsavaluebasedonthegivenarguments.Hereisaverysimpleexample: >>>>>>defadd_one(number): ...returnnumber+1 >>>add_one(2) 3 Ingeneral,functionsinPythonmayalsohavesideeffectsratherthanjustturninganinputintoanoutput.Theprint()functionisabasicexampleofthis:itreturnsNonewhilehavingthesideeffectofoutputtingsomethingtotheconsole.However,tounderstanddecorators,itisenoughtothinkaboutfunctionsassomethingthatturnsgivenargumentsintoavalue. Note:Infunctionalprogramming,youwork(almost)onlywithpurefunctionswithoutsideeffects.Whilenotapurelyfunctionallanguage,Pythonsupportsmanyofthefunctionalprogrammingconcepts,includingfunctionsasfirst-classobjects. RemoveadsFirst-ClassObjects InPython,functionsarefirst-classobjects.Thismeansthatfunctionscanbepassedaroundandusedasarguments,justlikeanyotherobject(string,int,float,list,andsoon).Considerthefollowingthreefunctions: defsay_hello(name): returnf"Hello{name}" defbe_awesome(name): returnf"Yo{name},togetherwearetheawesomest!" defgreet_bob(greeter_func): returngreeter_func("Bob") Here,say_hello()andbe_awesome()areregularfunctionsthatexpectanamegivenasastring.Thegreet_bob()functionhowever,expectsafunctionasitsargument.Wecan,forinstance,passitthesay_hello()orthebe_awesome()function: >>>>>>greet_bob(say_hello) 'HelloBob' >>>greet_bob(be_awesome) 'YoBob,togetherwearetheawesomest!' Notethatgreet_bob(say_hello)referstotwofunctions,butindifferentways:greet_bob()andsay_hello.Thesay_hellofunctionisnamedwithoutparentheses.Thismeansthatonlyareferencetothefunctionispassed.Thefunctionisnotexecuted.Thegreet_bob()function,ontheotherhand,iswrittenwithparentheses,soitwillbecalledasusual. InnerFunctions It’spossibletodefinefunctionsinsideotherfunctions.Suchfunctionsarecalledinnerfunctions.Here’sanexampleofafunctionwithtwoinnerfunctions: defparent(): print("Printingfromtheparent()function") deffirst_child(): print("Printingfromthefirst_child()function") defsecond_child(): print("Printingfromthesecond_child()function") second_child() first_child() Whathappenswhenyoucalltheparent()function?Thinkaboutthisforaminute.Theoutputwillbeasfollows: >>>>>>parent() Printingfromtheparent()function Printingfromthesecond_child()function Printingfromthefirst_child()function Notethattheorderinwhichtheinnerfunctionsaredefineddoesnotmatter.Likewithanyotherfunctions,theprintingonlyhappenswhentheinnerfunctionsareexecuted. Furthermore,theinnerfunctionsarenotdefineduntiltheparentfunctioniscalled.Theyarelocallyscopedtoparent():theyonlyexistinsidetheparent()functionaslocalvariables.Trycallingfirst_child().Youshouldgetanerror: Traceback(mostrecentcalllast): File"",line1,in NameError:name'first_child'isnotdefined Wheneveryoucallparent(),theinnerfunctionsfirst_child()andsecond_child()arealsocalled.Butbecauseoftheirlocalscope,theyaren’tavailableoutsideoftheparent()function. ReturningFunctionsFromFunctions Pythonalsoallowsyoutousefunctionsasreturnvalues.Thefollowingexamplereturnsoneoftheinnerfunctionsfromtheouterparent()function: defparent(num): deffirst_child(): return"Hi,IamEmma" defsecond_child(): return"CallmeLiam" ifnum==1: returnfirst_child else: returnsecond_child Notethatyouarereturningfirst_childwithouttheparentheses.Recallthatthismeansthatyouarereturningareferencetothefunctionfirst_child.Incontrastfirst_child()withparenthesesreferstotheresultofevaluatingthefunction.Thiscanbeseeninthefollowingexample: >>>>>>first=parent(1) >>>second=parent(2) >>>first .first_childat0x7f599f1e2e18> >>>second .second_childat0x7f599dad5268> Thesomewhatcrypticoutputsimplymeansthatthefirstvariablereferstothelocalfirst_child()functioninsideofparent(),whilesecondpointstosecond_child(). Youcannowusefirstandsecondasiftheyareregularfunctions,eventhoughthefunctionstheypointtocan’tbeaccesseddirectly: >>>>>>first() 'Hi,IamEmma' >>>second() 'CallmeLiam' Finally,notethatintheearlierexampleyouexecutedtheinnerfunctionswithintheparentfunction,forinstancefirst_child().However,inthislastexample,youdidnotaddparenthesestotheinnerfunctions—first_child—uponreturning.Thatway,yougotareferencetoeachfunctionthatyoucouldcallinthefuture.Makesense? RemoveadsSimpleDecorators Nowthatyou’veseenthatfunctionsarejustlikeanyotherobjectinPython,you’rereadytomoveonandseethemagicalbeastthatisthePythondecorator.Let’sstartwithanexample: defmy_decorator(func): defwrapper(): print("Somethingishappeningbeforethefunctioniscalled.") func() print("Somethingishappeningafterthefunctioniscalled.") returnwrapper defsay_whee(): print("Whee!") say_whee=my_decorator(say_whee) Canyouguesswhathappenswhenyoucallsay_whee()?Tryit: >>>>>>say_whee() Somethingishappeningbeforethefunctioniscalled. Whee! Somethingishappeningafterthefunctioniscalled. Tounderstandwhat’sgoingonhere,lookbackatthepreviousexamples.Weareliterallyjustapplyingeverythingyouhavelearnedsofar. Theso-calleddecorationhappensatthefollowingline: say_whee=my_decorator(say_whee) Ineffect,thenamesay_wheenowpointstothewrapper()innerfunction.Rememberthatyoureturnwrapperasafunctionwhenyoucallmy_decorator(say_whee): >>>>>>say_whee .wrapperat0x7f3c5dfd42f0> However,wrapper()hasareferencetotheoriginalsay_whee()asfunc,andcallsthatfunctionbetweenthetwocallstoprint(). Putsimply:decoratorswrapafunction,modifyingitsbehavior. Beforemovingon,let’shavealookatasecondexample.Becausewrapper()isaregularPythonfunction,thewayadecoratormodifiesafunctioncanchangedynamically.Soasnottodisturbyourneighbors,thefollowingexamplewillonlyrunthedecoratedcodeduringtheday: fromdatetimeimportdatetime defnot_during_the_night(func): defwrapper(): if7<=datetime.now().hour<22: func() else: pass#Hush,theneighborsareasleep returnwrapper defsay_whee(): print("Whee!") say_whee=not_during_the_night(say_whee) Ifyoutrytocallsay_whee()afterbedtime,nothingwillhappen: >>>>>>say_whee() >>> SyntacticSugar! Thewayyoudecoratedsay_whee()aboveisalittleclunky.Firstofall,youenduptypingthenamesay_wheethreetimes.Inaddition,thedecorationgetsabithiddenawaybelowthedefinitionofthefunction. Instead,Pythonallowsyoutousedecoratorsinasimplerwaywiththe@symbol,sometimescalledthe“pie”syntax.Thefollowingexampledoestheexactsamethingasthefirstdecoratorexample: defmy_decorator(func): defwrapper(): print("Somethingishappeningbeforethefunctioniscalled.") func() print("Somethingishappeningafterthefunctioniscalled.") returnwrapper @my_decorator defsay_whee(): print("Whee!") So,@my_decoratorisjustaneasierwayofsayingsay_whee=my_decorator(say_whee).It’showyouapplyadecoratortoafunction. RemoveadsReusingDecorators RecallthatadecoratorisjustaregularPythonfunction.Alltheusualtoolsforeasyreusabilityareavailable.Let’smovethedecoratortoitsownmodulethatcanbeusedinmanyotherfunctions. Createafilecalleddecorators.pywiththefollowingcontent: defdo_twice(func): defwrapper_do_twice(): func() func() returnwrapper_do_twice Note:Youcannameyourinnerfunctionwhateveryouwant,andagenericnamelikewrapper()isusuallyokay.You’llseealotofdecoratorsinthisarticle.Tokeepthemapart,we’llnametheinnerfunctionwiththesamenameasthedecoratorbutwithawrapper_prefix. Youcannowusethisnewdecoratorinotherfilesbydoingaregularimport: fromdecoratorsimportdo_twice @do_twice defsay_whee(): print("Whee!") Whenyourunthisexample,youshouldseethattheoriginalsay_whee()isexecutedtwice: >>>>>>say_whee() Whee! Whee! FreeBonus:Clickheretogetaccesstoafree"ThePowerofPythonDecorators"guidethatshowsyouthreeadvanceddecoratorpatternsandtechniquesyoucanusetowritecleanerandmorePythonicprograms. DecoratingFunctionsWithArguments Saythatyouhaveafunctionthatacceptssomearguments.Canyoustilldecorateit?Let’stry: fromdecoratorsimportdo_twice @do_twice defgreet(name): print(f"Hello{name}") Unfortunately,runningthiscoderaisesanerror: >>>>>>greet("World") Traceback(mostrecentcalllast): File"",line1,in TypeError:wrapper_do_twice()takes0positionalargumentsbut1wasgiven Theproblemisthattheinnerfunctionwrapper_do_twice()doesnottakeanyarguments,butname="World"waspassedtoit.Youcouldfixthisbylettingwrapper_do_twice()acceptoneargument,butthenitwouldnotworkforthesay_whee()functionyoucreatedearlier. Thesolutionistouse*argsand**kwargsintheinnerwrapperfunction.Thenitwillacceptanarbitrarynumberofpositionalandkeywordarguments.Rewritedecorators.pyasfollows: defdo_twice(func): defwrapper_do_twice(*args,**kwargs): func(*args,**kwargs) func(*args,**kwargs) returnwrapper_do_twice Thewrapper_do_twice()innerfunctionnowacceptsanynumberofargumentsandpassesthemontothefunctionitdecorates.Nowbothyoursay_whee()andgreet()examplesworks: >>>>>>say_whee() Whee! Whee! >>>greet("World") HelloWorld HelloWorld RemoveadsReturningValuesFromDecoratedFunctions Whathappenstothereturnvalueofdecoratedfunctions?Well,that’suptothedecoratortodecide.Let’ssayyoudecorateasimplefunctionasfollows: fromdecoratorsimportdo_twice @do_twice defreturn_greeting(name): print("Creatinggreeting") returnf"Hi{name}" Trytouseit: >>>>>>hi_adam=return_greeting("Adam") Creatinggreeting Creatinggreeting >>>print(hi_adam) None Oops,yourdecoratoratethereturnvaluefromthefunction. Becausethedo_twice_wrapper()doesn’texplicitlyreturnavalue,thecallreturn_greeting("Adam")endedupreturningNone. Tofixthis,youneedtomakesurethewrapperfunctionreturnsthereturnvalueofthedecoratedfunction.Changeyourdecorators.pyfile: defdo_twice(func): defwrapper_do_twice(*args,**kwargs): func(*args,**kwargs) returnfunc(*args,**kwargs) returnwrapper_do_twice Thereturnvaluefromthelastexecutionofthefunctionisreturned: >>>>>>return_greeting("Adam") Creatinggreeting Creatinggreeting 'HiAdam' WhoAreYou,Really? AgreatconveniencewhenworkingwithPython,especiallyintheinteractiveshell,isitspowerfulintrospectionability.Introspectionistheabilityofanobjecttoknowaboutitsownattributesatruntime.Forinstance,afunctionknowsitsownnameanddocumentation: >>>>>>print >>>print.__name__ 'print' >>>help(print) Helponbuilt-infunctionprintinmodulebuiltins: print(...) Theintrospectionworksforfunctionsyoudefineyourselfaswell: >>>>>>say_whee .wrapper_do_twiceat0x7f43700e52f0> >>>say_whee.__name__ 'wrapper_do_twice' >>>help(say_whee) Helponfunctionwrapper_do_twiceinmoduledecorators: wrapper_do_twice() However,afterbeingdecorated,say_whee()hasgottenveryconfusedaboutitsidentity.Itnowreportsbeingthewrapper_do_twice()innerfunctioninsidethedo_twice()decorator.Althoughtechnicallytrue,thisisnotveryusefulinformation. Tofixthis,[email protected],whichwillpreserveinformationabouttheoriginalfunction.Updatedecorators.pyagain: importfunctools defdo_twice(func): @functools.wraps(func) defwrapper_do_twice(*args,**kwargs): func(*args,**kwargs) returnfunc(*args,**kwargs) returnwrapper_do_twice Youdonotneedtochangeanythingaboutthedecoratedsay_whee()function: >>>>>>say_whee >>>say_whee.__name__ 'say_whee' >>>help(say_whee) Helponfunctionsay_wheeinmodulewhee: say_whee() Muchbetter!Nowsay_whee()isstillitselfafterdecoration. TechnicalDetail:[email protected]_wrapper()toupdatespecialattributeslike__name__and__doc__thatareusedintheintrospection. RemoveadsAFewRealWorldExamples Let’slookatafewmoreusefulexamplesofdecorators.You’llnoticethatthey’llmainlyfollowthesamepatternthatyou’velearnedsofar: importfunctools defdecorator(func): @functools.wraps(func) defwrapper_decorator(*args,**kwargs): #Dosomethingbefore value=func(*args,**kwargs) #Dosomethingafter returnvalue returnwrapper_decorator Thisformulaisagoodboilerplatetemplateforbuildingmorecomplexdecorators. Note:Inlaterexamples,wewillassumethatthesedecoratorsaresavedinyourdecorators.pyfileaswell.Recallthatyoucandownloadalltheexamplesinthistutorial. TimingFunctions Let’sstartbycreatinga@timerdecorator.Itwillmeasurethetimeafunctiontakestoexecuteandprintthedurationtotheconsole.Here’sthecode: importfunctools importtime deftimer(func): """Printtheruntimeofthedecoratedfunction""" @functools.wraps(func) defwrapper_timer(*args,**kwargs): start_time=time.perf_counter()#1 value=func(*args,**kwargs) end_time=time.perf_counter()#2 run_time=end_time-start_time#3 print(f"Finished{func.__name__!r}in{run_time:.4f}secs") returnvalue returnwrapper_timer @timer defwaste_some_time(num_times): for_inrange(num_times): sum([i**2foriinrange(10000)]) Thisdecoratorworksbystoringthetimejustbeforethefunctionstartsrunning(atthelinemarked#1)andjustafterthefunctionfinishes(at#2).Thetimethefunctiontakesisthenthedifferencebetweenthetwo(at#3).Weusethetime.perf_counter()function,whichdoesagoodjobofmeasuringtimeintervals.Herearesomeexamplesoftimings: >>>>>>waste_some_time(1) Finished'waste_some_time'in0.0010secs >>>waste_some_time(999) Finished'waste_some_time'in0.3260secs Runityourself.Workthroughthecodelinebyline.Makesureyouunderstandhowitworks.Don’tworryifyoudon’tgetit,though.Decoratorsareadvancedbeings.Trytosleeponitormakeadrawingoftheprogramflow. Note:The@timerdecoratorisgreatifyoujustwanttogetanideaabouttheruntimeofyourfunctions.Ifyouwanttodomoreprecisemeasurementsofcode,youshouldinsteadconsiderthetimeitmoduleinthestandardlibrary.Ittemporarilydisablesgarbagecollectionandrunsmultipletrialstostripoutnoisefromquickfunctioncalls. DebuggingCode Thefollowing@debugdecoratorwillprinttheargumentsafunctioniscalledwithaswellasitsreturnvalueeverytimethefunctioniscalled: importfunctools defdebug(func): """Printthefunctionsignatureandreturnvalue""" @functools.wraps(func) defwrapper_debug(*args,**kwargs): args_repr=[repr(a)forainargs]#1 kwargs_repr=[f"{k}={v!r}"fork,vinkwargs.items()]#2 signature=",".join(args_repr+kwargs_repr)#3 print(f"Calling{func.__name__}({signature})") value=func(*args,**kwargs) print(f"{func.__name__!r}returned{value!r}")#4 returnvalue returnwrapper_debug Thesignatureiscreatedbyjoiningthestringrepresentationsofallthearguments.Thenumbersinthefollowinglistcorrespondtothenumberedcommentsinthecode: Createalistofthepositionalarguments.Userepr()togetanicestringrepresentingeachargument. Createalistofthekeywordarguments.Thef-stringformatseachargumentaskey=valuewherethe!rspecifiermeansthatrepr()isusedtorepresentthevalue. Thelistsofpositionalandkeywordargumentsisjoinedtogethertoonesignaturestringwitheachargumentseparatedbyacomma. Thereturnvalueisprintedafterthefunctionisexecuted. Let’sseehowthedecoratorworksinpracticebyapplyingittoasimplefunctionwithonepositionandonekeywordargument: @debug defmake_greeting(name,age=None): ifageisNone: returnf"Howdy{name}!" else: returnf"Whoa{name}!{age}already,youaregrowingup!" Notehowthe@debugdecoratorprintsthesignatureandreturnvalueofthemake_greeting()function: >>>>>>make_greeting("Benjamin") Callingmake_greeting('Benjamin') 'make_greeting'returned'HowdyBenjamin!' 'HowdyBenjamin!' >>>make_greeting("Richard",age=112) Callingmake_greeting('Richard',age=112) 'make_greeting'returned'WhoaRichard!112already,youaregrowingup!' 'WhoaRichard!112already,youaregrowingup!' >>>make_greeting(name="Dorrisile",age=116) Callingmake_greeting(name='Dorrisile',age=116) 'make_greeting'returned'WhoaDorrisile!116already,youaregrowingup!' 'WhoaDorrisile!116already,youaregrowingup!' Thisexamplemightnotseemimmediatelyusefulsincethe@debugdecoratorjustrepeatswhatyoujustwrote.It’smorepowerfulwhenappliedtosmallconveniencefunctionsthatyoudon’tcalldirectlyyourself. Thefollowingexamplecalculatesanapproximationtothemathematicalconstante: importmath fromdecoratorsimportdebug #Applyadecoratortoastandardlibraryfunction math.factorial=debug(math.factorial) defapproximate_e(terms=18): returnsum(1/math.factorial(n)forninrange(terms)) Thisexamplealsoshowshowyoucanapplyadecoratortoafunctionthathasalreadybeendefined.Theapproximationofeisbasedonthefollowingseriesexpansion: Whencallingtheapproximate_e()function,youcanseethe@debugdecoratoratwork: >>>>>>approximate_e(5) Callingfactorial(0) 'factorial'returned1 Callingfactorial(1) 'factorial'returned1 Callingfactorial(2) 'factorial'returned2 Callingfactorial(3) 'factorial'returned6 Callingfactorial(4) 'factorial'returned24 2.708333333333333 Inthisexample,yougetadecentapproximationtothetruevaluee=2.718281828,addingonly5terms. RemoveadsSlowingDownCode Thisnextexamplemightnotseemveryuseful.WhywouldyouwanttoslowdownyourPythoncode?Probablythemostcommonusecaseisthatyouwanttorate-limitafunctionthatcontinuouslycheckswhetheraresource—likeawebpage—haschanged.The@slow_downdecoratorwillsleeponesecondbeforeitcallsthedecoratedfunction: importfunctools importtime defslow_down(func): """Sleep1secondbeforecallingthefunction""" @functools.wraps(func) defwrapper_slow_down(*args,**kwargs): time.sleep(1) returnfunc(*args,**kwargs) returnwrapper_slow_down @slow_down defcountdown(from_number): iffrom_number<1: print("Liftoff!") else: print(from_number) countdown(from_number-1) Toseetheeffectofthe@slow_downdecorator,youreallyneedtoruntheexampleyourself: >>>>>>countdown(3) 3 2 1 Liftoff! Note:Thecountdown()functionisarecursivefunction.Inotherwords,it’safunctioncallingitself.TolearnmoreaboutrecursivefunctionsinPython,seeourguideonThinkingRecursivelyinPython. The@slow_downdecoratoralwayssleepsforonesecond.Later,you’llseehowtocontroltheratebypassinganargumenttothedecorator. RegisteringPlugins Decoratorsdon’thavetowrapthefunctionthey’redecorating.Theycanalsosimplyregisterthatafunctionexistsandreturnitunwrapped.Thiscanbeused,forinstance,tocreatealight-weightplug-inarchitecture: importrandom PLUGINS=dict() defregister(func): """Registerafunctionasaplug-in""" PLUGINS[func.__name__]=func returnfunc @register defsay_hello(name): returnf"Hello{name}" @register defbe_awesome(name): returnf"Yo{name},togetherwearetheawesomest!" defrandomly_greet(name): greeter,greeter_func=random.choice(list(PLUGINS.items())) print(f"Using{greeter!r}") returngreeter_func(name) The@registerdecoratorsimplystoresareferencetothedecoratedfunctionintheglobalPLUGINSdict.Notethatyoudonothavetowriteaninnerfunctionoruse@functools.wrapsinthisexamplebecauseyouarereturningtheoriginalfunctionunmodified. Therandomly_greet()functionrandomlychoosesoneoftheregisteredfunctionstouse.NotethatthePLUGINSdictionaryalreadycontainsreferencestoeachfunctionobjectthatisregisteredasaplugin: >>>>>>PLUGINS {'say_hello':, 'be_awesome':} >>>randomly_greet("Alice") Using'say_hello' 'HelloAlice' Themainbenefitofthissimplepluginarchitectureisthatyoudonotneedtomaintainalistofwhichpluginsexist.Thatlistiscreatedwhenthepluginsregisterthemselves.Thismakesittrivialtoaddanewplugin:justdefinethefunctionanddecorateitwith@register. Ifyouarefamiliarwithglobals()inPython,youmightseesomesimilaritiestohowthepluginarchitectureworks.globals()givesaccesstoallglobalvariablesinthecurrentscope,includingyourplugins: >>>>>>globals() {...,#Lotsofvariablesnotshownhere. 'say_hello':, 'be_awesome':, 'randomly_greet':} Usingthe@registerdecorator,youcancreateyourowncuratedlistofinterestingvariables,effectivelyhand-pickingsomefunctionsfromglobals(). IstheUserLoggedIn? Thefinalexamplebeforemovingontosomefancierdecoratorsiscommonlyusedwhenworkingwithawebframework.Inthisexample,weareusingFlasktosetupa/secretwebpagethatshouldonlybevisibletousersthatareloggedinorotherwiseauthenticated: fromflaskimportFlask,g,request,redirect,url_for importfunctools app=Flask(__name__) deflogin_required(func): """Makesureuserisloggedinbeforeproceeding""" @functools.wraps(func) defwrapper_login_required(*args,**kwargs): ifg.userisNone: returnredirect(url_for("login",next=request.url)) returnfunc(*args,**kwargs) returnwrapper_login_required @app.route("/secret") @login_required defsecret(): ... Whilethisgivesanideaabouthowtoaddauthenticationtoyourwebframework,youshouldusuallynotwritethesetypesofdecoratorsyourself.ForFlask,youcanusetheFlask-Loginextensioninstead,whichaddsmoresecurityandfunctionality. RemoveadsFancyDecorators Sofar,you’veseenhowtocreatesimpledecorators.Youalreadyhaveaprettygoodunderstandingofwhatdecoratorsareandhowtheywork.Feelfreetotakeabreakfromthisarticletopracticeeverythingyou’velearned. Inthesecondpartofthistutorial,we’llexploremoreadvancedfeatures,includinghowtousethefollowing: Decoratorsonclasses Severaldecoratorsononefunction Decoratorswitharguments Decoratorsthatcanoptionallytakearguments Statefuldecorators Classesasdecorators DecoratingClasses Therearetwodifferentwaysyoucanusedecoratorsonclasses.Thefirstoneisveryclosetowhatyouhavealreadydonewithfunctions:youcandecoratethemethodsofaclass.Thiswasoneofthemotivationsforintroducingdecoratorsbackintheday. Somecommonlyuseddecoratorsthatareevenbuilt-insinPythonare@classmethod,@staticmethod,[email protected]@classmethodand@staticmethoddecoratorsareusedtodefinemethodsinsideaclassnamespacethatarenotconnectedtoaparticularinstanceofthatclass.The@propertydecoratorisusedtocustomizegettersandsettersforclassattributes.Expandtheboxbelowforanexampleusingthesedecorators. Exampleusingbuilt-inclassdecoratorsShow/Hide ThefollowingdefinitionofaCircleclassusesthe@classmethod,@staticmethod,and@propertydecorators: classCircle: def__init__(self,radius): self._radius=radius @property defradius(self): """Getvalueofradius""" returnself._radius @radius.setter defradius(self,value): """Setradius,raiseerrorifnegative""" ifvalue>=0: self._radius=value else: raiseValueError("Radiusmustbepositive") @property defarea(self): """Calculateareainsidecircle""" returnself.pi()*self.radius**2 defcylinder_volume(self,height): """Calculatevolumeofcylinderwithcircleasbase""" returnself.area*height @classmethod defunit_circle(cls): """Factorymethodcreatingacirclewithradius1""" returncls(1) @staticmethod defpi(): """Valueofπ,couldusemath.piinsteadthough""" return3.1415926535 Inthisclass: .cylinder_volume()isaregularmethod. .radiusisamutableproperty:itcanbesettoadifferentvalue.However,bydefiningasettermethod,wecandosomeerrortestingtomakesureit’snotsettoanonsensicalnegativenumber.Propertiesareaccessedasattributeswithoutparentheses. .areaisanimmutableproperty:propertieswithout.setter()methodscan’tbechanged.Eventhoughitisdefinedasamethod,itcanberetrievedasanattributewithoutparentheses. .unit_circle()isaclassmethod.It’snotboundtooneparticularinstanceofCircle.Classmethodsareoftenusedasfactorymethodsthatcancreatespecificinstancesoftheclass. .pi()isastaticmethod.It’snotreallydependentontheCircleclass,exceptthatitispartofitsnamespace.Staticmethodscanbecalledoneitheraninstanceortheclass. TheCircleclasscanforexamplebeusedasfollows: >>>>>>c=Circle(5) >>>c.radius 5 >>>c.area 78.5398163375 >>>c.radius=2 >>>c.area 12.566370614 >>>c.area=100 AttributeError:can'tsetattribute >>>c.cylinder_volume(height=4) 50.265482456 >>>c.radius=-1 ValueError:Radiusmustbepositive >>>c=Circle.unit_circle() >>>c.radius 1 >>>c.pi() 3.1415926535 >>>Circle.pi() 3.1415926535 Let’sdefineaclasswherewedecoratesomeofitsmethodsusingthe@debugand@timerdecoratorsfromearlier: fromdecoratorsimportdebug,timer classTimeWaster: @debug def__init__(self,max_num): self.max_num=max_num @timer defwaste_time(self,num_times): for_inrange(num_times): sum([i**2foriinrange(self.max_num)]) Usingthisclass,youcanseetheeffectofthedecorators: >>>>>>tw=TimeWaster(1000) Calling__init__(,1000) '__init__'returnedNone >>>tw.waste_time(999) Finished'waste_time'in0.3376secs Theotherwaytousedecoratorsonclassesistodecoratethewholeclass.Thisis,forexample,doneinthenewdataclassesmoduleinPython3.7: fromdataclassesimportdataclass @dataclass classPlayingCard: rank:str suit:str Themeaningofthesyntaxissimilartothefunctiondecorators.Intheexampleabove,youcouldhavedonethedecorationbywritingPlayingCard=dataclass(PlayingCard). Acommonuseofclassdecoratorsistobeasimpleralternativetosomeuse-casesofmetaclasses.Inbothcases,youarechangingthedefinitionofaclassdynamically. Writingaclassdecoratorisverysimilartowritingafunctiondecorator.Theonlydifferenceisthatthedecoratorwillreceiveaclassandnotafunctionasanargument.Infact,allthedecoratorsyousawabovewillworkasclassdecorators.Whenyouareusingthemonaclassinsteadofafunction,theireffectmightnotbewhatyouwant.Inthefollowingexample,the@timerdecoratorisappliedtoaclass: fromdecoratorsimporttimer @timer classTimeWaster: def__init__(self,max_num): self.max_num=max_num defwaste_time(self,num_times): for_inrange(num_times): sum([i**2foriinrange(self.max_num)]) Decoratingaclassdoesnotdecorateitsmethods.Recallthat@timerisjustshorthandforTimeWaster=timer(TimeWaster). Here,@timeronlymeasuresthetimeittakestoinstantiatetheclass: >>>>>>tw=TimeWaster(1000) Finished'TimeWaster'in0.0000secs >>>tw.waste_time(999) >>> Later,youwillseeanexampledefiningaproperclassdecorator,namely@singleton,whichensuresthatthereisonlyoneinstanceofaclass. RemoveadsNestingDecorators Youcanapplyseveraldecoratorstoafunctionbystackingthemontopofeachother: fromdecoratorsimportdebug,do_twice @debug @do_twice defgreet(name): print(f"Hello{name}") Thinkaboutthisasthedecoratorsbeingexecutedintheordertheyarelisted.Inotherwords,@debugcalls@do_twice,whichcallsgreet(),ordebug(do_twice(greet())): >>>>>>greet("Eva") Callinggreet('Eva') HelloEva HelloEva 'greet'returnedNone Observethedifferenceifwechangetheorderof@debugand@do_twice: fromdecoratorsimportdebug,do_twice @do_twice @debug defgreet(name): print(f"Hello{name}") Inthiscase,@do_twicewillbeappliedto@debugaswell: >>>>>>greet("Eva") Callinggreet('Eva') HelloEva 'greet'returnedNone Callinggreet('Eva') HelloEva 'greet'returnedNone DecoratorsWithArguments Sometimes,it’susefultopassargumentstoyourdecorators.Forinstance,@do_twicecouldbeextendedtoa@repeat(num_times)decorator.Thenumberoftimestoexecutethedecoratedfunctioncouldthenbegivenasanargument. Thiswouldallowyoutodosomethinglikethis: @repeat(num_times=4) defgreet(name): print(f"Hello{name}") >>>>>>greet("World") HelloWorld HelloWorld HelloWorld HelloWorld Thinkabouthowyoucouldachievethis. Sofar,thenamewrittenafterthe@hasreferredtoafunctionobjectthatcanbecalledwithanotherfunction.Tobeconsistent,youthenneedrepeat(num_times=4)toreturnafunctionobjectthatcanactasadecorator.Luckily,youalreadyknowhowtoreturnfunctions!Ingeneral,youwantsomethinglikethefollowing: defrepeat(num_times): defdecorator_repeat(func): ...#Createandreturnawrapperfunction returndecorator_repeat Typically,thedecoratorcreatesandreturnsaninnerwrapperfunction,sowritingtheexampleoutinfullwillgiveyouaninnerfunctionwithinaninnerfunction.WhilethismightsoundliketheprogrammingequivalentoftheInceptionmovie,we’lluntangleitallinamoment: defrepeat(num_times): defdecorator_repeat(func): @functools.wraps(func) defwrapper_repeat(*args,**kwargs): for_inrange(num_times): value=func(*args,**kwargs) returnvalue returnwrapper_repeat returndecorator_repeat Itlooksalittlemessy,butwehaveonlyputthesamedecoratorpatternyouhaveseenmanytimesbynowinsideoneadditionaldefthathandlestheargumentstothedecorator.Let’sstartwiththeinnermostfunction: defwrapper_repeat(*args,**kwargs): for_inrange(num_times): value=func(*args,**kwargs) returnvalue Thiswrapper_repeat()functiontakesarbitraryargumentsandreturnsthevalueofthedecoratedfunction,func().Thiswrapperfunctionalsocontainstheloopthatcallsthedecoratedfunctionnum_timestimes.Thisisnodifferentfromtheearlierwrapperfunctionsyouhaveseen,exceptthatitisusingthenum_timesparameterthatmustbesuppliedfromtheoutside. Onestepout,you’llfindthedecoratorfunction: defdecorator_repeat(func): @functools.wraps(func) defwrapper_repeat(*args,**kwargs): ... returnwrapper_repeat Again,decorator_repeat()looksexactlylikethedecoratorfunctionsyouhavewrittenearlier,exceptthatit’snameddifferently.That’sbecausewereservethebasename—repeat()—fortheoutermostfunction,whichistheonetheuserwillcall. Asyouhavealreadyseen,theoutermostfunctionreturnsareferencetothedecoratorfunction: defrepeat(num_times): defdecorator_repeat(func): ... returndecorator_repeat Thereareafewsubtlethingshappeningintherepeat()function: Definingdecorator_repeat()asaninnerfunctionmeansthatrepeat()willrefertoafunctionobject—decorator_repeat.Earlier,weusedrepeatwithoutparenthesestorefertothefunctionobject.Theaddedparenthesesarenecessarywhendefiningdecoratorsthattakearguments. Thenum_timesargumentisseeminglynotusedinrepeat()itself.Butbypassingnum_timesaclosureiscreatedwherethevalueofnum_timesisstoreduntilitwillbeusedlaterbywrapper_repeat(). Witheverythingsetup,let’sseeiftheresultsareasexpected: @repeat(num_times=4) defgreet(name): print(f"Hello{name}") >>>>>>greet("World") HelloWorld HelloWorld HelloWorld HelloWorld Justtheresultwewereaimingfor. RemoveadsBothPlease,ButNeverMindtheBread Withalittlebitofcare,youcanalsodefinedecoratorsthatcanbeusedbothwithandwithoutarguments.Mostlikely,youdon’tneedthis,butitisnicetohavetheflexibility. Asyousawintheprevioussection,whenadecoratorusesarguments,youneedtoaddanextraouterfunction.Thechallengeisforyourcodetofigureoutifthedecoratorhasbeencalledwithorwithoutarguments. Sincethefunctiontodecorateisonlypassedindirectlyifthedecoratoriscalledwithoutarguments,thefunctionmustbeanoptionalargument.Thismeansthatthedecoratorargumentsmustallbespecifiedbykeyword.Youcanenforcethiswiththespecial*syntax,whichmeansthatallfollowingparametersarekeyword-only: defname(_func=None,*,kw1=val1,kw2=val2,...):#1 defdecorator_name(func): ...#Createandreturnawrapperfunction. if_funcisNone: returndecorator_name#2 else: returndecorator_name(_func)#3 Here,the_funcargumentactsasamarker,notingwhetherthedecoratorhasbeencalledwithargumentsornot: Ifnamehasbeencalledwithoutarguments,thedecoratedfunctionwillbepassedinas_func.Ifithasbeencalledwitharguments,then_funcwillbeNone,andsomeofthekeywordargumentsmayhavebeenchangedfromtheirdefaultvalues.The*intheargumentlistmeansthattheremainingargumentscan’tbecalledaspositionalarguments. Inthiscase,thedecoratorwascalledwitharguments.Returnadecoratorfunctionthatcanreadandreturnafunction. Inthiscase,thedecoratorwascalledwithoutarguments.Applythedecoratortothefunctionimmediately. Usingthisboilerplateonthe@repeatdecoratorintheprevioussection,youcanwritethefollowing: defrepeat(_func=None,*,num_times=2): defdecorator_repeat(func): @functools.wraps(func) defwrapper_repeat(*args,**kwargs): for_inrange(num_times): value=func(*args,**kwargs) returnvalue returnwrapper_repeat if_funcisNone: returndecorator_repeat else: returndecorator_repeat(_func) [email protected]_funcparameterandtheif-elseattheend. Recipe9.6oftheexcellentPythonCookbookshowsanalternativesolutionusingfunctools.partial(). Theseexamplesshowthat@repeatcannowbeusedwithorwithoutarguments: @repeat defsay_whee(): print("Whee!") @repeat(num_times=3) defgreet(name): print(f"Hello{name}") Recallthatthedefaultvalueofnum_timesis2: >>>>>>say_whee() Whee! Whee! >>>greet("Penny") HelloPenny HelloPenny HelloPenny StatefulDecorators Sometimes,it’susefultohaveadecoratorthatcankeeptrackofstate.Asasimpleexample,wewillcreateadecoratorthatcountsthenumberoftimesafunctioniscalled. Note:Inthebeginningofthisguide,wetalkedaboutpurefunctionsreturningavaluebasedongivenarguments.Statefuldecoratorsarequitetheopposite,wherethereturnvaluewilldependonthecurrentstate,aswellasthegivenarguments. Inthenextsection,youwillseehowtouseclassestokeepstate.Butinsimplecases,youcanalsogetawaywithusingfunctionattributes: importfunctools defcount_calls(func): @functools.wraps(func) defwrapper_count_calls(*args,**kwargs): wrapper_count_calls.num_calls+=1 print(f"Call{wrapper_count_calls.num_calls}of{func.__name__!r}") returnfunc(*args,**kwargs) wrapper_count_calls.num_calls=0 returnwrapper_count_calls @count_calls defsay_whee(): print("Whee!") Thestate—thenumberofcallstothefunction—isstoredinthefunctionattribute.num_callsonthewrapperfunction.Hereistheeffectofusingit: >>>>>>say_whee() Call1of'say_whee' Whee! >>>say_whee() Call2of'say_whee' Whee! >>>say_whee.num_calls 2 RemoveadsClassesasDecorators Thetypicalwaytomaintainstateisbyusingclasses.Inthissection,you’llseehowtorewritethe@count_callsexamplefromtheprevioussectionusingaclassasadecorator. Recallthatthedecoratorsyntax@my_decoratorisjustaneasierwayofsayingfunc=my_decorator(func).Therefore,ifmy_decoratorisaclass,itneedstotakefuncasanargumentinits.__init__()method.Furthermore,theclassinstanceneedstobecallablesothatitcanstandinforthedecoratedfunction. Foraclassinstancetobecallable,youimplementthespecial.__call__()method: classCounter: def__init__(self,start=0): self.count=start def__call__(self): self.count+=1 print(f"Currentcountis{self.count}") The.__call__()methodisexecutedeachtimeyoutrytocallaninstanceoftheclass: >>>>>>counter=Counter() >>>counter() Currentcountis1 >>>counter() Currentcountis2 >>>counter.count 2 Therefore,atypicalimplementationofadecoratorclassneedstoimplement.__init__()and.__call__(): importfunctools classCountCalls: def__init__(self,func): functools.update_wrapper(self,func) self.func=func self.num_calls=0 def__call__(self,*args,**kwargs): self.num_calls+=1 print(f"Call{self.num_calls}of{self.func.__name__!r}") returnself.func(*args,**kwargs) @CountCalls defsay_whee(): print("Whee!") The.__init__()methodmuststoreareferencetothefunctionandcandoanyothernecessaryinitialization.The.__call__()methodwillbecalledinsteadofthedecoratedfunction.Itdoesessentiallythesamethingasthewrapper()functioninourearlierexamples.Notethatyouneedtousethefunctools.update_wrapper()[email protected]. This@CountCallsdecoratorworksthesameastheoneintheprevioussection: >>>>>>say_whee() Call1of'say_whee' Whee! >>>say_whee() Call2of'say_whee' Whee! >>>say_whee.num_calls 2 MoreRealWorldExamples We’vecomeafarwaynow,havingfiguredouthowtocreateallkindsofdecorators.Let’swrapitup,puttingournewfoundknowledgeintocreatingafewmoreexamplesthatmightactuallybeusefulintherealworld. SlowingDownCode,Revisited Asnotedearlier,ourpreviousimplementationof@slow_downalwayssleepsforonesecond.Nowyouknowhowtoaddparameterstodecorators,solet’srewrite@slow_downusinganoptionalrateargumentthatcontrolshowlongitsleeps: importfunctools importtime defslow_down(_func=None,*,rate=1): """Sleepgivenamountofsecondsbeforecallingthefunction""" defdecorator_slow_down(func): @functools.wraps(func) defwrapper_slow_down(*args,**kwargs): time.sleep(rate) returnfunc(*args,**kwargs) returnwrapper_slow_down if_funcisNone: returndecorator_slow_down else: returndecorator_slow_down(_func) We’reusingtheboilerplateintroducedintheBothPlease,ButNeverMindtheBreadsectiontomake@slow_downcallablebothwithandwithoutarguments.Thesamerecursivecountdown()functionasearliernowsleepstwosecondsbetweeneachcount: @slow_down(rate=2) defcountdown(from_number): iffrom_number<1: print("Liftoff!") else: print(from_number) countdown(from_number-1) Asbefore,youmustruntheexampleyourselftoseetheeffectofthedecorator: >>>>>>countdown(3) 3 2 1 Liftoff! CreatingSingletons Asingletonisaclasswithonlyoneinstance.ThereareseveralsingletonsinPythonthatyouusefrequently,includingNone,True,andFalse.ItisthefactthatNoneisasingletonthatallowsyoutocompareforNoneusingtheiskeyword,likeyousawintheBothPleasesection: if_funcisNone: returndecorator_name else: returndecorator_name(_func) UsingisreturnsTrueonlyforobjectsthataretheexactsameinstance.Thefollowing@singletondecoratorturnsaclassintoasingletonbystoringthefirstinstanceoftheclassasanattribute.Laterattemptsatcreatinganinstancesimplyreturnthestoredinstance: importfunctools defsingleton(cls): """MakeaclassaSingletonclass(onlyoneinstance)""" @functools.wraps(cls) defwrapper_singleton(*args,**kwargs): ifnotwrapper_singleton.instance: wrapper_singleton.instance=cls(*args,**kwargs) returnwrapper_singleton.instance wrapper_singleton.instance=None returnwrapper_singleton @singleton classTheOne: pass Asyousee,thisclassdecoratorfollowsthesametemplateasourfunctiondecorators.Theonlydifferenceisthatweareusingclsinsteadoffuncastheparameternametoindicatethatitismeanttobeaclassdecorator. Let’sseeifitworks: >>>>>>first_one=TheOne() >>>another_one=TheOne() >>>id(first_one) 140094218762280 >>>id(another_one) 140094218762280 >>>first_oneisanother_one True Itseemsclearthatfirst_oneisindeedtheexactsameinstanceasanother_one. Note:SingletonclassesarenotreallyusedasofteninPythonasinotherlanguages.Theeffectofasingletonisusuallybetterimplementedasaglobalvariableinamodule. CachingReturnValues Decoratorscanprovideanicemechanismforcachingandmemoization.Asanexample,let’slookatarecursivedefinitionoftheFibonaccisequence: fromdecoratorsimportcount_calls @count_calls deffibonacci(num): ifnum<2: returnnum returnfibonacci(num-1)+fibonacci(num-2) Whiletheimplementationissimple,itsruntimeperformanceisterrible: >>>>>>fibonacci(10) 55 >>>fibonacci.num_calls 177 TocalculatethetenthFibonaccinumber,youshouldreallyonlyneedtocalculatetheprecedingFibonaccinumbers,butthisimplementationsomehowneedsawhopping177calculations.Itgetsworsequickly:21891calculationsareneededforfibonacci(20)andalmost2.7millioncalculationsforthe30thnumber.ThisisbecausethecodekeepsrecalculatingFibonaccinumbersthatarealreadyknown. TheusualsolutionistoimplementFibonaccinumbersusingaforloopandalookuptable.However,simplecachingofthecalculationswillalsodothetrick: importfunctools fromdecoratorsimportcount_calls defcache(func): """Keepacacheofpreviousfunctioncalls""" @functools.wraps(func) defwrapper_cache(*args,**kwargs): cache_key=args+tuple(kwargs.items()) ifcache_keynotinwrapper_cache.cache: wrapper_cache.cache[cache_key]=func(*args,**kwargs) returnwrapper_cache.cache[cache_key] wrapper_cache.cache=dict() returnwrapper_cache @cache @count_calls deffibonacci(num): ifnum<2: returnnum returnfibonacci(num-1)+fibonacci(num-2) Thecacheworksasalookuptable,sonowfibonacci()onlydoesthenecessarycalculationsonce: >>>>>>fibonacci(10) Call1of'fibonacci' ... Call11of'fibonacci' 55 >>>fibonacci(8) 21 Notethatinthefinalcalltofibonacci(8),nonewcalculationswereneeded,sincetheeighthFibonaccinumberhadalreadybeencalculatedforfibonacci(10). Inthestandardlibrary,aLeastRecentlyUsed(LRU)[email protected]_cache. Thisdecoratorhasmorefeaturesthantheoneyousawabove.Youshoulduse@functools.lru_cacheinsteadofwritingyourowncachedecorator: importfunctools @functools.lru_cache(maxsize=4) deffibonacci(num): print(f"Calculatingfibonacci({num})") ifnum<2: returnnum returnfibonacci(num-1)+fibonacci(num-2) Themaxsizeparameterspecifieshowmanyrecentcallsarecached.Thedefaultvalueis128,butyoucanspecifymaxsize=Nonetocacheallfunctioncalls.However,beawarethatthiscancausememoryproblemsifyouarecachingmanylargeobjects. Youcanusethe.cache_info()methodtoseehowthecacheperforms,andyoucantuneitifneeded.Inourexample,weusedanartificiallysmallmaxsizetoseetheeffectofelementsbeingremovedfromthecache: >>>>>>fibonacci(10) Calculatingfibonacci(10) Calculatingfibonacci(9) Calculatingfibonacci(8) Calculatingfibonacci(7) Calculatingfibonacci(6) Calculatingfibonacci(5) Calculatingfibonacci(4) Calculatingfibonacci(3) Calculatingfibonacci(2) Calculatingfibonacci(1) Calculatingfibonacci(0) 55 >>>fibonacci(8) 21 >>>fibonacci(5) Calculatingfibonacci(5) Calculatingfibonacci(4) Calculatingfibonacci(3) Calculatingfibonacci(2) Calculatingfibonacci(1) Calculatingfibonacci(0) 5 >>>fibonacci(8) Calculatingfibonacci(8) Calculatingfibonacci(7) Calculatingfibonacci(6) 21 >>>fibonacci(5) 5 >>>fibonacci.cache_info() CacheInfo(hits=17,misses=20,maxsize=4,currsize=4) AddingInformationAboutUnits ThefollowingexampleissomewhatsimilartotheRegisteringPluginsexamplefromearlier,inthatitdoesnotreallychangethebehaviorofthedecoratedfunction.Instead,itsimplyaddsunitasafunctionattribute: defset_unit(unit): """Registeraunitonafunction""" defdecorator_set_unit(func): func.unit=unit returnfunc returndecorator_set_unit Thefollowingexamplecalculatesthevolumeofacylinderbasedonitsradiusandheightincentimeters: importmath @set_unit("cm^3") defvolume(radius,height): returnmath.pi*radius**2*height This.unitfunctionattributecanlaterbeaccessedwhenneeded: >>>>>>volume(3,5) 141.3716694115407 >>>volume.unit 'cm^3' Notethatyoucouldhaveachievedsomethingsimilarusingfunctionannotations: importmath defvolume(radius,height)->"cm^3": returnmath.pi*radius**2*height However,sinceannotationsareusedfortypehints,itwouldbehardtocombinesuchunitsasannotationswithstatictypechecking. Unitsbecomeevenmorepowerfulandfunwhenconnectedwithalibrarythatcanconvertbetweenunits.Onesuchlibraryispint.Withpintinstalled(pipinstallPint),youcanforinstanceconvertthevolumetocubicinchesorgallons: >>>>>>importpint >>>ureg=pint.UnitRegistry() >>>vol=volume(3,5)*ureg(volume.unit) >>>vol >>>vol.to("cubicinches") >>>vol.to("gallons").m#Magnitude 0.0373464440537444 YoucouldalsomodifythedecoratortoreturnapintQuantitydirectly.SuchaQuantityismadebymultiplyingavaluewiththeunit.Inpint,unitsmustbelookedupinaUnitRegistry.Theregistryisstoredasafunctionattributetoavoidclutteringthenamespace: defuse_unit(unit): """HaveafunctionreturnaQuantitywithgivenunit""" use_unit.ureg=pint.UnitRegistry() defdecorator_use_unit(func): @functools.wraps(func) defwrapper_use_unit(*args,**kwargs): value=func(*args,**kwargs) returnvalue*use_unit.ureg(unit) returnwrapper_use_unit returndecorator_use_unit @use_unit("meterspersecond") defaverage_speed(distance,duration): returndistance/duration Withthe@use_unitdecorator,convertingunitsispracticallyeffortless: >>>>>>bolt=average_speed(100,9.58) >>>bolt >>>bolt.to("kmperhour") >>>bolt.to("mph").m#Magnitude 23.350065679064745 ValidatingJSON Let’slookatonelastusecase.TakeaquicklookatthefollowingFlaskroutehandler: @app.route("/grade",methods=["POST"]) defupdate_grade(): json_data=request.get_json() if"student_id"notinjson_data: abort(400) #Updatedatabase return"success!" Hereweensurethatthekeystudent_idispartoftherequest.Althoughthisvalidationworks,itreallydoesnotbelonginthefunctionitself.Plus,perhapsthereareotherroutesthatusetheexactsamevalidation.So,let’skeepitDRYandabstractoutanyunnecessarylogicwithadecorator.Thefollowing@validate_jsondecoratorwilldothejob: fromflaskimportFlask,request,abort importfunctools app=Flask(__name__) defvalidate_json(*expected_args):#1 defdecorator_validate_json(func): @functools.wraps(func) defwrapper_validate_json(*args,**kwargs): json_object=request.get_json() forexpected_arginexpected_args:#2 ifexpected_argnotinjson_object: abort(400) returnfunc(*args,**kwargs) returnwrapper_validate_json returndecorator_validate_json Intheabovecode,thedecoratortakesavariablelengthlistasanargumentsothatwecanpassinasmanystringargumentsasnecessary,eachrepresentingakeyusedtovalidatetheJSONdata: ThelistofkeysthatmustbepresentintheJSONisgivenasargumentstothedecorator. ThewrapperfunctionvalidatesthateachexpectedkeyispresentintheJSONdata. Theroutehandlercanthenfocusonitsrealjob—updatinggrades—asitcansafelyassumethatJSONdataarevalid: @app.route("/grade",methods=["POST"]) @validate_json("student_id") defupdate_grade(): json_data=request.get_json() #Updatedatabase. return"success!" Conclusion Thishasbeenquiteajourney!Youstartedthistutorialbylookingalittlecloseratfunctions,particularlyhowtheycanbedefinedinsideotherfunctionsandpassedaroundjustlikeanyotherPythonobject.Thenyoulearnedaboutdecoratorsandhowtowritethemsuchthat: Theycanbereused. Theycandecoratefunctionswithargumentsandreturnvalues. [email protected]. Inthesecondpartofthetutorial,yousawmoreadvanceddecoratorsandlearnedhowto: Decorateclasses Nestdecorators Addargumentstodecorators Keepstatewithindecorators Useclassesasdecorators Yousawthat,todefineadecorator,youtypicallydefineafunctionreturningawrapperfunction.Thewrapperfunctionuses*argsand**kwargstopassonargumentstothedecoratedfunction.Ifyouwantyourdecoratortoalsotakearguments,youneedtonestthewrapperfunctioninsideanotherfunction.Inthiscase,youusuallyendupwiththreereturnstatements. Youcanfindthecodefromthistutorialonline. FurtherReading Ifyouarestilllookingformore,ourbookPythonTrickshasasectionondecorators,asdoesthePythonCookbookbyDavidBeazleyandBrianK.Jones. ForadeepdiveintothehistoricaldiscussiononhowdecoratorsshouldbeimplementedinPython,seePEP318aswellasthePythonDecoratorWiki.MoreexamplesofdecoratorscanbefoundinthePythonDecoratorLibrary.Thedecoratormodulecansimplifycreatingyourowndecorators,anditsdocumentationcontainsfurtherdecoratorexamples. Also,we’veputtogetherashort&sweetPythondecoratorscheatsheetforyou: DecoratorsCheatSheet:Clickheretogetaccesstoafreethree-pagePythondecoratorscheatsheetthatsummarizesthetechniquesexplainedinthistutorial. MarkasCompleted WatchNowThistutorialhasarelatedvideocoursecreatedbytheRealPythonteam.Watchittogetherwiththewrittentutorialtodeepenyourunderstanding:PythonDecorators101 🐍PythonTricks💌 Getashort&sweetPythonTrickdeliveredtoyourinboxeverycoupleofdays.Nospamever.Unsubscribeanytime.CuratedbytheRealPythonteam. SendMePythonTricks» AboutGeirArneHjelle GeirArneisanavidPythonistaandamemberoftheRealPythontutorialteam. »MoreaboutGeirArne EachtutorialatRealPythoniscreatedbyateamofdeveloperssothatitmeetsourhighqualitystandards.Theteammemberswhoworkedonthistutorialare: Aldren Brad Dan Joanna Michael MasterReal-WorldPythonSkillsWithUnlimitedAccesstoReal Python Joinusandgetaccesstohundredsoftutorials,hands-onvideocourses,andacommunityofexpert Pythonistas: LevelUpYourPythonSkills» MasterReal-WorldPythonSkillsWithUnlimitedAccesstoReal Python Joinusandgetaccesstohundredsoftutorials,hands-onvideocourses,andacommunityofexpertPythonistas: LevelUpYourPythonSkills» WhatDoYouThink? Tweet Share Email RealPythonCommentPolicy:Themostusefulcommentsarethosewrittenwiththegoaloflearningfromorhelpingoutotherreaders—afterreadingthewholearticleandalltheearliercomments.Complaintsandinsultsgenerallywon’tmakethecuthere. What’syour#1takeawayorfavoritethingyoulearned?Howareyougoingtoputyournewfoundskillstouse?Leaveacommentbelowandletusknow. KeepLearning RelatedTutorialCategories: intermediate python RecommendedVideoCourse:PythonDecorators101 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 Functions First-ClassObjects InnerFunctions ReturningFunctionsFromFunctions SimpleDecorators SyntacticSugar! ReusingDecorators DecoratingFunctionsWithArguments ReturningValuesFromDecoratedFunctions WhoAreYou,Really? AFewRealWorldExamples TimingFunctions DebuggingCode SlowingDownCode RegisteringPlugins IstheUserLoggedIn? FancyDecorators DecoratingClasses NestingDecorators DecoratorsWithArguments BothPlease,ButNeverMindtheBread StatefulDecorators ClassesasDecorators MoreRealWorldExamples SlowingDownCode,Revisited CreatingSingletons CachingReturnValues AddingInformationAboutUnits ValidatingJSON Conclusion FurtherReading MarkasCompleted Tweet Share Email RecommendedVideoCoursePythonDecorators101 Almostthere!Completethisformandclickthebuttonbelowtogaininstantaccess: × PythonDecoratorsCheatSheet(PDF) SendCheatSheet» Almostthere!Completethisformandclickthebuttonbelowtogaininstantaccess: × ThePowerofPythonDecorators:AdvancedPatterns&Techniques(PDFGuide) GetthePythonDecoratorsGuide» Almostthere!Completethisformandclickthebuttonbelowtogaininstantaccess: × PythonDecoratorsQ&ATranscript(PDF) SendMyPDF»



請為這篇文章評分?