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»