Decorators are everywhere in Python. They're functions that take another function as an argument and return a new function with added ...
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
Python'sproperty():AddManagedAttributestoYourClasses
byLeodanisPozoRamos
Oct13,2021
best-practices
intermediate
python
MarkasCompleted
Tweet
Share
Email
TableofContents
ManagingAttributesinYourClasses
TheGetterandSetterApproachinPython
ThePythonicApproach
GettingStartedWithPython’sproperty()
CreatingAttributesWithproperty()
Usingproperty()asaDecorator
ProvidingRead-OnlyAttributes
CreatingRead-WriteAttributes
ProvidingWrite-OnlyAttributes
PuttingPython’sproperty()IntoAction
ValidatingInputValues
ProvidingComputedAttributes
CachingComputedAttributes
LoggingAttributeAccessandMutation
ManagingAttributeDeletion
CreatingBackward-CompatibleClassAPIs
OverridingPropertiesinSubclasses
Conclusion
Removeads
WithPython’sproperty(),youcancreatemanagedattributesinyourclasses.Youcanusemanagedattributes,alsoknownasproperties,whenyouneedtomodifytheirinternalimplementationwithoutchangingthepublicAPIoftheclass.ProvidingstableAPIscanhelpyouavoidbreakingyourusers’codewhentheyrelyonyourclassesandobjects.
PropertiesarearguablythemostpopularwaytocreatemanagedattributesquicklyandinthepurestPythonicstyle.
Inthistutorial,you’lllearnhowto:
Createmanagedattributesorpropertiesinyourclasses
Performlazyattributeevaluationandprovidecomputedattributes
AvoidsetterandgettermethodstomakeyourclassesmorePythonic
Createread-only,read-write,andwrite-onlyproperties
Createconsistentandbackward-compatibleAPIsforyourclasses
You’llalsowriteafewpracticalexamplesthatuseproperty()forvalidatinginputdata,computingattributevaluesdynamically,loggingyourcode,andmore.Togetthemostoutofthistutorial,youshouldknowthebasicsofobject-orientedprogramminganddecoratorsinPython.
FreeBonus:5ThoughtsOnPythonMastery,afreecourseforPythondevelopersthatshowsyoutheroadmapandthemindsetyou’llneedtotakeyourPythonskillstothenextlevel.
ManagingAttributesinYourClasses
Whenyoudefineaclassinanobject-orientedprogramminglanguage,you’llprobablyendupwithsomeinstanceandclassattributes.Inotherwords,you’llendupwithvariablesthatareaccessiblethroughtheinstance,class,orevenboth,dependingonthelanguage.Attributesrepresentorholdtheinternalstateofagivenobject,whichyou’lloftenneedtoaccessandmutate.
Typically,youhaveatleasttwowaystomanageanattribute.Eitheryoucanaccessandmutatetheattributedirectlyoryoucanusemethods.Methodsarefunctionsattachedtoagivenclass.Theyprovidethebehaviorsandactionsthatanobjectcanperformwithitsinternaldataandattributes.
Ifyouexposeyourattributestotheuser,thentheybecomepartofthepublicAPIofyourclasses.Youruserwillaccessandmutatethemdirectlyintheircode.Theproblemcomeswhenyouneedtochangetheinternalimplementationofagivenattribute.
Sayyou’reworkingonaCircleclass.Theinitialimplementationhasasingleattributecalled.radius.Youfinishcodingtheclassandmakeitavailabletoyourendusers.TheystartusingCircleintheircodetocreatealotofawesomeprojectsandapplications.Goodjob!
Nowsupposethatyouhaveanimportantuserthatcomestoyouwithanewrequirement.Theydon’twantCircletostoretheradiusanylonger.Theyneedapublic.diameterattribute.
Atthispoint,removing.radiustostartusing.diametercouldbreakthecodeofsomeofyourendusers.Youneedtomanagethissituationinawayotherthanremoving.radius.
ProgramminglanguagessuchasJavaandC++encourageyoutoneverexposeyourattributestoavoidthiskindofproblem.Instead,youshouldprovidegetterandsettermethods,alsoknownasaccessorsandmutators,respectively.ThesemethodsofferawaytochangetheinternalimplementationofyourattributeswithoutchangingyourpublicAPI.
Note:Getterandsettermethodsareoftenconsideredananti-patternandasignalofpoorobject-orienteddesign.Themainargumentbehindthispropositionisthatthesemethodsbreakencapsulation.Theyallowyoutoaccessandmutatethecomponentsofyourobjects.
Intheend,theselanguagesneedgetterandsettermethodsbecausetheydon’tprovideasuitablewaytochangetheinternalimplementationofanattributeifagivenrequirementchanges.ChangingtheinternalimplementationwouldrequireanAPImodification,whichcanbreakyourendusers’code.
RemoveadsTheGetterandSetterApproachinPython
Technically,there’snothingthatstopsyoufromusinggetterandsettermethodsinPython.Here’showthisapproachwouldlook:
#point.py
classPoint:
def__init__(self,x,y):
self._x=x
self._y=y
defget_x(self):
returnself._x
defset_x(self,value):
self._x=value
defget_y(self):
returnself._y
defset_y(self,value):
self._y=value
Inthisexample,youcreatePointwithtwonon-publicattributes._xand._ytoholdtheCartesiancoordinatesofthepointathand.
Note:Pythondoesn’thavethenotionofaccessmodifiers,suchasprivate,protected,andpublic,torestrictaccesstoattributesandmethods.InPython,thedistinctionisbetweenpublicandnon-publicclassmembers.
Ifyouwanttosignalthatagivenattributeormethodisnon-public,thenyouhavetousethewell-knownPythonconventionofprefixingthenamewithanunderscore(_).That’sthereasonbehindthenamingoftheattributes._xand._y.
Notethatthisisjustaconvention.Itdoesn’tstopyouandotherprogrammersfromaccessingtheattributesusingdotnotation,asinobj._attr.However,it’sbadpracticetoviolatethisconvention.
Toaccessandmutatethevalueofeither._xor._y,youcanusethecorrespondinggetterandsettermethods.GoaheadandsavetheabovedefinitionofPointinaPythonmoduleandimporttheclassintoyourinteractiveshell.
Here’showyoucanworkwithPointinyourcode:
>>>>>>frompointimportPoint
>>>point=Point(12,5)
>>>point.get_x()
12
>>>point.get_y()
5
>>>point.set_x(42)
>>>point.get_x()
42
>>>#Non-publicattributesarestillaccessible
>>>point._x
42
>>>point._y
5
With.get_x()and.get_y(),youcanaccessthecurrentvaluesof._xand._y.Youcanusethesettermethodtostoreanewvalueinthecorrespondingmanagedattribute.Fromthiscode,youcanconfirmthatPythondoesn’trestrictaccesstonon-publicattributes.Whetherornotyoudosoisuptoyou.
ThePythonicApproach
EventhoughtheexampleyoujustsawusesthePythoncodingstyle,itdoesn’tlookPythonic.Intheexample,thegetterandsettermethodsdon’tperformanyfurtherprocessingwith._xand._y.YoucanrewritePointinamoreconciseandPythonicway:
>>>>>>classPoint:
...def__init__(self,x,y):
...self.x=x
...self.y=y
...
>>>point=Point(12,5)
>>>point.x
12
>>>point.y
5
>>>point.x=42
>>>point.x
42
Thiscodeuncoversafundamentalprinciple.ExposingattributestotheenduserisnormalandcommoninPython.Youdon’tneedtoclutteryourclasseswithgetterandsettermethodsallthetime,whichsoundsprettycool!However,howcanyouhandlerequirementchangesthatwouldseemtoinvolveAPIchanges?
UnlikeJavaandC++,PythonprovideshandytoolsthatallowyoutochangetheunderlyingimplementationofyourattributeswithoutchangingyourpublicAPI.Themostpopularapproachistoturnyourattributesintoproperties.
Note:Anothercommonapproachtoprovidemanagedattributesistousedescriptors.Inthistutorial,however,you’lllearnaboutproperties.
Propertiesrepresentanintermediatefunctionalitybetweenaplainattribute(orfield)andamethod.Inotherwords,theyallowyoutocreatemethodsthatbehavelikeattributes.Withproperties,youcanchangehowyoucomputethetargetattributewheneveryouneedtodoso.
Forexample,youcanturnboth.xand.yintoproperties.Withthischange,youcancontinueaccessingthemasattributes.You’llalsohaveanunderlyingmethodholding.xand.ythatwillallowyoutomodifytheirinternalimplementationandperformactionsonthemrightbeforeyourusersaccessandmutatethem.
Note:Propertiesaren’texclusivetoPython.LanguagessuchasJavaScript,C#,Kotlin,andothersalsoprovidetoolsandtechniquesforcreatingpropertiesasclassmembers.
ThemainadvantageofPythonpropertiesisthattheyallowyoutoexposeyourattributesaspartofyourpublicAPI.Ifyoueverneedtochangetheunderlyingimplementation,thenyoucanturntheattributeintoapropertyatanytimewithoutmuchpain.
Inthefollowingsections,you’lllearnhowtocreatepropertiesinPython.
RemoveadsGettingStartedWithPython’sproperty()
Python’sproperty()isthePythonicwaytoavoidformalgetterandsettermethodsinyourcode.Thisfunctionallowsyoutoturnclassattributesintopropertiesormanagedattributes.Sinceproperty()isabuilt-infunction,youcanuseitwithoutimportinganything.Additionally,property()wasimplementedinCtoensureoptimalperformance.
Note:It’scommontorefertoproperty()asabuilt-infunction.However,propertyisaclassdesignedtoworkasafunctionratherthanasaregularclass.That’swhymostPythondeveloperscallitafunction.That’salsothereasonwhyproperty()doesn’tfollowthePythonconventionfornamingclasses.
Thistutorialfollowsthecommonpracticesofcallingproperty()afunctionratherthanaclass.However,insomesections,you’llseeitcalledaclasstofacilitatetheexplanation.
Withproperty(),youcanattachgetterandsettermethodstogivenclassattributes.Thisway,youcanhandletheinternalimplementationforthatattributewithoutexposinggetterandsettermethodsinyourAPI.Youcanalsospecifyawaytohandleattributedeletionandprovideanappropriatedocstringforyourproperties.
Here’sthefullsignatureofproperty():
property(fget=None,fset=None,fdel=None,doc=None)
Thefirsttwoargumentstakefunctionobjectsthatwillplaytheroleofgetter(fget)andsetter(fset)methods.Here’sasummaryofwhateachargumentdoes:
Argument
Description
fget
Functionthatreturnsthevalueofthemanagedattribute
fset
Functionthatallowsyoutosetthevalueofthemanagedattribute
fdel
Functiontodefinehowthemanagedattributehandlesdeletion
doc
Stringrepresentingtheproperty’sdocstring
Thereturnvalueofproperty()isthemanagedattributeitself.Ifyouaccessthemanagedattribute,asinobj.attr,thenPythonautomaticallycallsfget().Ifyouassignanewvaluetotheattribute,asinobj.attr=value,thenPythoncallsfset()usingtheinputvalueasanargument.Finally,ifyourunadelobj.attrstatement,thenPythonautomaticallycallsfdel().
Note:Thefirstthreeargumentstoproperty()takefunctionobjects.Youcanthinkofafunctionobjectasthefunctionnamewithoutthecallingpairofparentheses.
Youcanusedoctoprovideanappropriatedocstringforyourproperties.YouandyourfellowprogrammerswillbeabletoreadthatdocstringusingPython’shelp().Thedocargumentisalsousefulwhenyou’reworkingwithcodeeditorsandIDEsthatsupportdocstringaccess.
Youcanuseproperty()eitherasafunctionoradecoratortobuildyourproperties.Inthefollowingtwosections,you’lllearnhowtousebothapproaches.However,youshouldknowupfrontthatthedecoratorapproachismorepopularinthePythoncommunity.
CreatingAttributesWithproperty()
Youcancreateapropertybycallingproperty()withanappropriatesetofargumentsandassigningitsreturnvaluetoaclassattribute.Alltheargumentstoproperty()areoptional.However,youtypicallyprovideatleastasetterfunction.
ThefollowingexampleshowshowtocreateaCircleclasswithahandypropertytomanageitsradius:
#circle.py
classCircle:
def__init__(self,radius):
self._radius=radius
def_get_radius(self):
print("Getradius")
returnself._radius
def_set_radius(self,value):
print("Setradius")
self._radius=value
def_del_radius(self):
print("Deleteradius")
delself._radius
radius=property(
fget=_get_radius,
fset=_set_radius,
fdel=_del_radius,
doc="Theradiusproperty."
)
Inthiscodesnippet,youcreateCircle.Theclassinitializer,.__init__(),takesradiusasanargumentandstoresitinanon-publicattributecalled._radius.Thenyoudefinethreenon-publicmethods:
._get_radius()returnsthecurrentvalueof._radius
._set_radius()takesvalueasanargumentandassignsitto._radius
._del_radius()deletestheinstanceattribute._radius
Onceyouhavethesethreemethodsinplace,youcreateaclassattributecalled.radiustostorethepropertyobject.Toinitializetheproperty,youpassthethreemethodsasargumentstoproperty().Youalsopassasuitabledocstringforyourproperty.
Inthisexample,youusekeywordargumentstoimprovethecodereadabilityandpreventconfusion.Thatway,youknowexactlywhichmethodgoesintoeachargument.
TogiveCircleatry,runthefollowingcodeinyourPythonshell:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.radius
Getradius
42.0
>>>circle.radius=100.0
Setradius
>>>circle.radius
Getradius
100.0
>>>delcircle.radius
Deleteradius
>>>circle.radius
Getradius
Traceback(mostrecentcalllast):
...
AttributeError:'Circle'objecthasnoattribute'_radius'
>>>help(circle)
HelponCircleinmodule__main__object:
classCircle(builtins.object)
...
|radius
|Theradiusproperty.
The.radiuspropertyhidesthenon-publicinstanceattribute._radius,whichisnowyourmanagedattributeinthisexample.Youcanaccessandassign.radiusdirectly.Internally,Pythonautomaticallycalls._get_radius()and._set_radius()whenneeded.Whenyouexecutedelcircle.radius,Pythoncalls._del_radius(),whichdeletestheunderlying._radius.
UsinglambdaFunctionsasGetterMethodsShow/Hide
Besidesusingregularnamedfunctionstoprovidegettermethodsinyourproperties,youcanalsouselambdafunctions.
Here’saversionofCircleinwhichthe.radiuspropertyusesalambdafunctionasitsgettermethod:
>>>>>>classCircle:
...def__init__(self,radius):
...self._radius=radius
...radius=property(lambdaself:self._radius)
...
>>>circle=Circle(42.0)
>>>circle.radius
42.0
Ifthefunctionalityofyourgettermethodislimitedtojustreturningthecurrentvalueofthemanagedattribute,thenusingalambdafunctioncanbeahandyapproach.
Propertiesareclassattributesthatmanageinstanceattributes.Youcanthinkofapropertyasacollectionofmethodsbundledtogether.Ifyouinspect.radiuscarefully,thenyoucanfindtherawmethodsyouprovidedasthefget,fset,andfdelarguments:
>>>>>>fromcircleimportCircle
>>>Circle.radius.fget
>>>Circle.radius.fset
>>>Circle.radius.fdel
>>>dir(Circle.radius)
[...,'__get__',...,'__set__',...]
Youcanaccessthegetter,setter,anddeletermethodsinagivenpropertythroughthecorresponding.fget,.fset,and.fdel.
Propertiesarealsooverridingdescriptors.Ifyouusedir()tochecktheinternalmembersofagivenproperty,thenyou’llfind.__set__()and.__get__()inthelist.Thesemethodsprovideadefaultimplementationofthedescriptorprotocol.
Note:Ifyouwanttobetterunderstandtheinternalimplementationofpropertyasaclass,thencheckoutthepurePythonPropertyclassdescribedinthedocumentation.
Thedefaultimplementationof.__set__(),forexample,runswhenyoudon’tprovideacustomsettermethod.Inthiscase,yougetanAttributeErrorbecausethere’snowaytosettheunderlyingproperty.
RemoveadsUsingproperty()asaDecorator
DecoratorsareeverywhereinPython.They’refunctionsthattakeanotherfunctionasanargumentandreturnanewfunctionwithaddedfunctionality.Withadecorator,youcanattachpre-andpost-processingoperationstoanexistingfunction.
WhenPython2.2introducedproperty(),thedecoratorsyntaxwasn’tavailable.Theonlywaytodefinepropertieswastopassgetter,setter,anddeletermethods,asyoulearnedbefore.ThedecoratorsyntaxwasaddedinPython2.4,andnowadays,usingproperty()asadecoratoristhemostpopularpracticeinthePythoncommunity.
Thedecoratorsyntaxconsistsofplacingthenameofthedecoratorfunctionwithaleading@symbolrightbeforethedefinitionofthefunctionyouwanttodecorate:
@decorator
deffunc(a):
returna
Inthiscodefragment,@decoratorcanbeafunctionorclassintendedtodecoratefunc().Thissyntaxisequivalenttothefollowing:
deffunc(a):
returna
func=decorator(func)
Thefinallineofcodereassignsthenamefunctoholdtheresultofcallingdecorator(func).Notethatthisisthesamesyntaxyouusedtocreateapropertyinthesectionabove.
Python’sproperty()canalsoworkasadecorator,soyoucanusethe@propertysyntaxtocreateyourpropertiesquickly:
1#circle.py
2
3classCircle:
4def__init__(self,radius):
5self._radius=radius
6
7@property
8defradius(self):
9"""Theradiusproperty."""
10print("Getradius")
11returnself._radius
12
[email protected]
14defradius(self,value):
15print("Setradius")
16self._radius=value
17
[email protected]
19defradius(self):
20print("Deleteradius")
21delself._radius
Thiscodelooksprettydifferentfromthegetterandsettermethodsapproach.CirclenowlooksmorePythonicandclean.Youdon’tneedtousemethodnamessuchas._get_radius(),._set_radius(),and._del_radius()anymore.Nowyouhavethreemethodswiththesamecleananddescriptiveattribute-likename.Howisthatpossible?
Thedecoratorapproachforcreatingpropertiesrequiresdefiningafirstmethodusingthepublicnamefortheunderlyingmanagedattribute,whichis.radiusinthiscase.Thismethodshouldimplementthegetterlogic.Intheaboveexample,lines7to11implementthatmethod.
Lines13to16definethesettermethodfor.radius.Inthiscase,thesyntaxisfairlydifferent.Insteadofusing@propertyagain,[email protected]?Takeanotherlookatthedir()output:
>>>>>>dir(Circle.radius)
[...,'deleter',...,'getter','setter']
Besides.fget,.fset,.fdel,andabunchofotherspecialattributesandmethods,propertyalsoprovides.deleter(),.getter(),and.setter().Thesethreemethodseachreturnanewproperty.
Whenyoudecoratethesecond.radius()[email protected](line13),youcreateanewpropertyandreassigntheclass-levelname.radius(line8)toholdit.Thisnewpropertycontainsthesamesetofmethodsoftheinitialpropertyatline8withtheadditionofthenewsettermethodprovidedonline14.Finally,thedecoratorsyntaxreassignsthenewpropertytothe.radiusclass-levelname.
Themechanismtodefinethedeletermethodissimilar.Thistime,[email protected],yougetafull-fledgedpropertywiththegetter,setter,anddeletermethods.
Finally,howcanyouprovidesuitabledocstringsforyourpropertieswhenyouusethedecoratorapproach?IfyoucheckCircleagain,you’llnotethatyoualreadydidsobyaddingadocstringtothegettermethodonline9.
ThenewCircleimplementationworksthesameastheexampleinthesectionabove:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.radius
Getradius
42.0
>>>circle.radius=100.0
Setradius
>>>circle.radius
Getradius
100.0
>>>delcircle.radius
Deleteradius
>>>circle.radius
Getradius
Traceback(mostrecentcalllast):
...
AttributeError:'Circle'objecthasnoattribute'_radius'
>>>help(circle)
HelponCircleinmodule__main__object:
classCircle(builtins.object)
...
|radius
|Theradiusproperty.
Youdon’tneedtouseapairofparenthesesforcalling.radius()asamethod.Instead,youcanaccess.radiusasyouwouldaccessaregularattribute,whichistheprimaryuseofproperties.Theyallowyoutotreatmethodsasattributes,andtheytakecareofcallingtheunderlyingsetofmethodsautomatically.
Here’sarecapofsomeimportantpointstorememberwhenyou’recreatingpropertieswiththedecoratorapproach:
The@propertydecoratormustdecoratethegettermethod.
Thedocstringmustgointhegettermethod.
Thesetteranddeletermethodsmustbedecoratedwiththenameofthegettermethodplus.setterand.deleter,respectively.
Uptothispoint,you’vecreatedmanagedattributesusingproperty()asafunctionandasadecorator.IfyoucheckyourCircleimplementationssofar,thenyou’llnotethattheirgetterandsettermethodsdon’taddanyrealextraprocessingontopofyourattributes.
Ingeneral,youshouldavoidturningattributesthatdon’trequireextraprocessingintoproperties.Usingpropertiesinthosesituationscanmakeyourcode:
Unnecessarilyverbose
Confusingtootherdevelopers
Slowerthancodebasedonregularattributes
Unlessyouneedsomethingmorethanbareattributeaccess,don’twriteproperties.They’reawasteofCPUtime,andmoreimportantly,they’reawasteofyourtime.Finally,youshouldavoidwritingexplicitgetterandsettermethodsandthenwrappingtheminaproperty.Instead,[email protected]’scurrentlythemostPythonicwaytogo.
RemoveadsProvidingRead-OnlyAttributes
Probablythemostelementaryusecaseofproperty()istoprovideread-onlyattributesinyourclasses.SayyouneedanimmutablePointclassthatdoesn’tallowtheusertomutatetheoriginalvalueofitscoordinates,xandy.Toachievethisgoal,youcancreatePointlikeinthefollowingexample:
#point.py
classPoint:
def__init__(self,x,y):
self._x=x
self._y=y
@property
defx(self):
returnself._x
@property
defy(self):
returnself._y
Here,youstoretheinputargumentsintheattributes._xand._y.Asyoualreadylearned,usingtheleadingunderscore(_)innamestellsotherdevelopersthatthey’renon-publicattributesandshouldn’tbeaccessedusingdotnotation,suchasinpoint._x.Finally,youdefinetwogettermethodsanddecoratethemwith@property.
Nowyouhavetworead-onlyproperties,.xand.y,asyourcoordinates:
>>>>>>frompointimportPoint
>>>point=Point(12,5)
>>>#Readcoordinates
>>>point.x
12
>>>point.y
5
>>>#Writecoordinates
>>>point.x=42
Traceback(mostrecentcalllast):
...
AttributeError:can'tsetattribute
Here,point.xandpoint.yarebare-boneexamplesofread-onlyproperties.Theirbehaviorreliesontheunderlyingdescriptorthatpropertyprovides.Asyoualreadysaw,thedefault.__set__()implementationraisesanAttributeErrorwhenyoudon’tdefineapropersettermethod.
YoucantakethisimplementationofPointalittlebitfurtherandprovideexplicitsettermethodsthatraiseacustomexceptionwithmoreelaborateandspecificmessages:
#point.py
classWriteCoordinateError(Exception):
pass
classPoint:
def__init__(self,x,y):
self._x=x
self._y=y
@property
defx(self):
returnself._x
@x.setter
defx(self,value):
raiseWriteCoordinateError("xcoordinateisread-only")
@property
defy(self):
returnself._y
@y.setter
defy(self,value):
raiseWriteCoordinateError("ycoordinateisread-only")
Inthisexample,youdefineacustomexceptioncalledWriteCoordinateError.ThisexceptionallowsyoutocustomizethewayyouimplementyourimmutablePointclass.Now,bothsettermethodsraiseyourcustomexceptionwithamoreexplicitmessage.GoaheadandgiveyourimprovedPointatry!
CreatingRead-WriteAttributes
Youcanalsouseproperty()toprovidemanagedattributeswithread-writecapabilities.Inpractice,youjustneedtoprovidetheappropriategettermethod(“read”)andsettermethod(“write”)toyourpropertiesinordertocreateread-writemanagedattributes.
SayyouwantyourCircleclasstohavea.diameterattribute.However,takingtheradiusandthediameterintheclassinitializerseemsunnecessarybecauseyoucancomputetheoneusingtheother.Here’saCirclethatmanages.radiusand.diameterasread-writeattributes:
#circle.py
importmath
classCircle:
def__init__(self,radius):
self.radius=radius
@property
defradius(self):
returnself._radius
@radius.setter
defradius(self,value):
self._radius=float(value)
@property
defdiameter(self):
returnself.radius*2
@diameter.setter
defdiameter(self,value):
self.radius=value/2
Here,youcreateaCircleclasswitharead-write.radius.Inthiscase,thegettermethodjustreturnstheradiusvalue.Thesettermethodconvertstheinputvaluefortheradiusandassignsittothenon-public._radius,whichisthevariableyouusetostorethefinaldata.
ThereisasubtledetailtonoteinthisnewimplementationofCircleandits.radiusattribute.Inthiscase,theclassinitializerassignstheinputvaluetothe.radiuspropertydirectlyinsteadofstoringitinadedicatednon-publicattribute,suchas._radius.
Why?Becauseyouneedtomakesurethateveryvalueprovidedasaradius,includingtheinitializationvalue,goesthroughthesettermethodandgetsconvertedtoafloating-pointnumber.
Circlealsoimplementsa.diameterattributeasaproperty.Thegettermethodcomputesthediameterusingtheradius.Thesettermethoddoessomethingcurious.Insteadofstoringtheinputdiametervalueinadedicatedattribute,itcalculatestheradiusandwritestheresultinto.radius.
Here’showyourCircleworks:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42)
>>>circle.radius
42.0
>>>circle.diameter
84.0
>>>circle.diameter=100
>>>circle.diameter
100.0
>>>circle.radius
50.0
Both.radiusand.diameterworkasnormalattributesintheseexamples,providingacleanandPythonicpublicAPIforyourCircleclass.
RemoveadsProvidingWrite-OnlyAttributes
Youcanalsocreatewrite-onlyattributesbytweakinghowyouimplementthegettermethodofyourproperties.Forexample,youcanmakeyourgettermethodraiseanexceptioneverytimeauseraccessestheunderlyingattributevalue.
Here’sanexampleofhandlingpasswordswithawrite-onlyproperty:
#users.py
importhashlib
importos
classUser:
def__init__(self,name,password):
self.name=name
self.password=password
@property
defpassword(self):
raiseAttributeError("Passwordiswrite-only")
@password.setter
defpassword(self,plaintext):
salt=os.urandom(32)
self._hashed_password=hashlib.pbkdf2_hmac(
"sha256",plaintext.encode("utf-8"),salt,100_000
)
TheinitializerofUsertakesausernameandapasswordasargumentsandstoresthemin.nameand.password,respectively.Youuseapropertytomanagehowyourclassprocessestheinputpassword.ThegettermethodraisesanAttributeErrorwheneverausertriestoretrievethecurrentpassword.Thisturns.passwordintoawrite-onlyattribute:
>>>>>>fromusersimportUser
>>>john=User("John","secret")
>>>john._hashed_password
b'b\xc7^ai\x9f3\xd2g...\x89^-\x92\xbe\xe6'
>>>john.password
Traceback(mostrecentcalllast):
...
AttributeError:Passwordiswrite-only
>>>john.password="supersecret"
>>>john._hashed_password
b'\xe9l$\x9f\xaf\x9d...b\xe8\xc8\xfcaU\r_'
Inthisexample,youcreatejohnasaUserinstancewithaninitialpassword.Thesettermethodhashesthepasswordandstoresitin._hashed_password.Notethatwhenyoutrytoaccess.passworddirectly,yougetanAttributeError.Finally,assigninganewvalueto.passwordtriggersthesettermethodandcreatesanewhashedpassword.
Inthesettermethodof.password,youuseos.urandom()togeneratea32-byterandomstringasyourhashingfunction’ssalt.Togeneratethehashedpassword,youusehashlib.pbkdf2_hmac().Thenyoustoretheresultinghashedpasswordinthenon-publicattribute._hashed_password.Doingsoensuresthatyouneversavetheplaintextpasswordinanyretrievableattribute.
PuttingPython’sproperty()IntoAction
Sofar,you’velearnedhowtousePython’sproperty()built-infunctiontocreatemanagedattributesinyourclasses.Youusedproperty()asafunctionandasadecoratorandlearnedaboutthedifferencesbetweenthesetwoapproaches.Youalsolearnedhowtocreateread-only,read-write,andwrite-onlyattributes.
Inthefollowingsections,you’llcodeafewexamplesthatwillhelpyougetabetterpracticalunderstandingofcommonusecasesofproperty().
ValidatingInputValues
Oneofthemostcommonusecasesofproperty()isbuildingmanagedattributesthatvalidatetheinputdatabeforestoringorevenacceptingitasasecureinput.Datavalidationisacommonrequirementincodethattakesinputfromusersorotherinformationsourcesthatyouconsideruntrusted.
Python’sproperty()providesaquickandreliabletoolfordealingwithinputdatavalidation.Forexample,thinkingbacktothePointexample,youmayrequirethevaluesof.xand.ytobevalidnumbers.Sinceyourusersarefreetoenteranytypeofdata,youneedtomakesurethatyourpointonlyacceptsnumbers.
Here’sanimplementationofPointthatmanagesthisrequirement:
#point.py
classPoint:
def__init__(self,x,y):
self.x=x
self.y=y
@property
defx(self):
returnself._x
@x.setter
defx(self,value):
try:
self._x=float(value)
print("Validated!")
exceptValueError:
raiseValueError('"x"mustbeanumber')fromNone
@property
defy(self):
returnself._y
@y.setter
defy(self,value):
try:
self._y=float(value)
print("Validated!")
exceptValueError:
raiseValueError('"y"mustbeanumber')fromNone
Thesettermethodsof.xand.yusetry…exceptblocksthatvalidateinputdatausingthePythonEAFPstyle.Ifthecalltofloat()succeeds,thentheinputdataisvalid,andyougetValidated!onyourscreen.Iffloat()raisesaValueError,thentheusergetsaValueErrorwithamorespecificmessage.
Note:Intheexampleabove,youusethesyntaxraise…fromNonetohideinternaldetailsrelatedtothecontextinwhichyou’reraisingtheexception.Fromtheenduser’sviewpoint,thesedetailscanbeconfusingandmakeyourclasslookunpolished.
Checkoutthesectionontheraisestatementinthedocumentationformoreinformationaboutthistopic.
It’simportanttonotethatassigningthe.xand.ypropertiesdirectlyin.__init__()ensuresthatthevalidationalsooccursduringobjectinitialization.Notdoingsoisacommonmistakewhenusingproperty()fordatavalidation.
Here’showyourPointclassworksnow:
>>>>>>frompointimportPoint
>>>point=Point(12,5)
Validated!
Validated!
>>>point.x
12.0
>>>point.y
5.0
>>>point.x=42
Validated!
>>>point.x
42.0
>>>point.y=100.0
Validated!
>>>point.y
100.0
>>>point.x="one"
Traceback(mostrecentcalllast):
...
ValueError:"x"mustbeanumber
>>>point.y="1o"
Traceback(mostrecentcalllast):
...
ValueError:"y"mustbeanumber
Ifyouassign.xand.yvaluesthatfloat()canturnintofloating-pointnumbers,thenthevalidationissuccessful,andthevalueisaccepted.Otherwise,yougetaValueError.
ThisimplementationofPointuncoversafundamentalweaknessofproperty().Didyouspotit?
That’sit!Youhaverepetitivecodethatfollowsspecificpatterns.ThisrepetitionbreakstheDRY(Don’tRepeatYourself)principle,soyouwouldwanttorefactorthiscodetoavoidit.Todoso,youcanabstractouttherepetitivelogicusingadescriptor:
#point.py
classCoordinate:
def__set_name__(self,owner,name):
self._name=name
def__get__(self,instance,owner):
returninstance.__dict__[self._name]
def__set__(self,instance,value):
try:
instance.__dict__[self._name]=float(value)
print("Validated!")
exceptValueError:
raiseValueError(f'"{self._name}"mustbeanumber')fromNone
classPoint:
x=Coordinate()
y=Coordinate()
def__init__(self,x,y):
self.x=x
self.y=y
Nowyourcodeisabitshorter.YoumanagedtoremoverepetitivecodebydefiningCoordinateasadescriptorthatmanagesyourdatavalidationinasingleplace.Thecodeworksjustlikeyourearlierimplementation.Goaheadandgiveitatry!
Ingeneral,ifyoufindyourselfcopyingandpastingpropertydefinitionsallaroundyourcodeorifyouspotrepetitivecodelikeintheexampleabove,thenyoushouldconsiderusingaproperdescriptor.
RemoveadsProvidingComputedAttributes
Ifyouneedanattributethatbuildsitsvaluedynamicallywheneveryouaccessit,thenproperty()isthewaytogo.Thesekindsofattributesarecommonlyknownascomputedattributes.They’rehandywhenyouneedthemtolooklikeeagerattributes,butyouwantthemtobelazy.
Themainreasonforcreatingeagerattributesistooptimizecomputationcostswhenyouaccesstheattributeoften.Ontheotherhand,ifyourarelyuseagivenattribute,thenalazypropertycanpostponeitscomputationuntilneeded,whichcanmakeyourprogramsmoreefficient.
Here’sanexampleofhowtouseproperty()tocreateacomputedattribute.areainaRectangleclass:
classRectangle:
def__init__(self,width,height):
self.width=width
self.height=height
@property
defarea(self):
returnself.width*self.height
Inthisexample,theRectangleinitializertakeswidthandheightasargumentsandstorestheminregularinstanceattributes.Theread-onlyproperty.areacomputesandreturnstheareaofthecurrentrectangleeverytimeyouaccessit.
Anothercommonusecaseofpropertiesistoprovideanauto-formattedvalueforagivenattribute:
classProduct:
def__init__(self,name,price):
self._name=name
self._price=float(price)
@property
defprice(self):
returnf"${self._price:,.2f}"
Inthisexample,.priceisapropertythatformatsandreturnsthepriceofaparticularproduct.Toprovideacurrency-likeformat,youuseanf-stringwithappropriateformattingoptions.
Note:Thisexampleusesfloating-pointnumberstorepresentcurrencies,whichisbadpractice.Instead,youshouldusedecimal.Decimalfromthestandardlibrary.
Asafinalexampleofcomputedattributes,sayyouhaveaPointclassthatuses.xand.yasCartesiancoordinates.Youwanttoprovidepolarcoordinatesforyourpointsothatyoucanusetheminafewcomputations.Thepolarcoordinatesystemrepresentseachpointusingthedistancetotheoriginandtheanglewiththehorizontalcoordinateaxis.
Here’saCartesiancoordinatesPointclassthatalsoprovidescomputedpolarcoordinates:
#point.py
importmath
classPoint:
def__init__(self,x,y):
self.x=x
self.y=y
@property
defdistance(self):
returnround(math.dist((0,0),(self.x,self.y)))
@property
defangle(self):
returnround(math.degrees(math.atan(self.y/self.x)),1)
defas_cartesian(self):
returnself.x,self.y
defas_polar(self):
returnself.distance,self.angle
ThisexampleshowshowtocomputethedistanceandangleofagivenPointobjectusingits.xand.yCartesiancoordinates.Here’showthiscodeworksinpractice:
>>>>>>frompointimportPoint
>>>point=Point(12,5)
>>>point.x
12
>>>point.y
5
>>>point.distance
13
>>>point.angle
22.6
>>>point.as_cartesian()
(12,5)
>>>point.as_polar()
(13,22.6)
Whenitcomestoprovidingcomputedorlazyattributes,property()isaprettyhandytool.However,ifyou’recreatinganattributethatyouusefrequently,thencomputingiteverytimecanbecostlyandwasteful.Agoodstrategyistocachethemoncethecomputationisdone.
CachingComputedAttributes
Sometimesyouhaveagivencomputedattributethatyouusefrequently.Constantlyrepeatingthesamecomputationmaybeunnecessaryandexpensive.Toworkaroundthisproblem,youcancachethecomputedvalueandsaveitinanon-publicdedicatedattributeforfurtherreuse.
Topreventunexpectedbehaviors,youneedtothinkofthemutabilityoftheinputdata.Ifyouhaveapropertythatcomputesitsvaluefromconstantinputvalues,thentheresultwillneverchange.Inthatcase,youcancomputethevaluejustonce:
#circle.py
fromtimeimportsleep
classCircle:
def__init__(self,radius):
self.radius=radius
self._diameter=None
@property
defdiameter(self):
ifself._diameterisNone:
sleep(0.5)#Simulateacostlycomputation
self._diameter=self.radius*2
returnself._diameter
EventhoughthisimplementationofCircleproperlycachesthecomputeddiameter,ithasthedrawbackthatifyoueverchangethevalueof.radius,then.diameterwon’treturnacorrectvalue:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.radius
42.0
>>>circle.diameter#Withdelay
84.0
>>>circle.diameter#Withoutdelay
84.0
>>>circle.radius=100.0
>>>circle.diameter#Wrongdiameter
84.0
Intheseexamples,youcreateacirclewitharadiusequalto42.0.The.diameterpropertycomputesitsvalueonlythefirsttimeyouaccessit.That’swhyyouseeadelayinthefirstexecutionandnodelayinthesecond.Notethateventhoughyouchangethevalueoftheradius,thediameterstaysthesame.
Iftheinputdataforacomputedattributemutates,thenyouneedtorecalculatetheattribute:
#circle.py
fromtimeimportsleep
classCircle:
def__init__(self,radius):
self.radius=radius
@property
defradius(self):
returnself._radius
@radius.setter
defradius(self,value):
self._diameter=None
self._radius=value
@property
defdiameter(self):
ifself._diameterisNone:
sleep(0.5)#Simulateacostlycomputation
self._diameter=self._radius*2
returnself._diameter
Thesettermethodofthe.radiuspropertyresets._diametertoNoneeverytimeyouchangethevalueoftheradius.Withthislittleupdate,.diameterrecalculatesitsvaluethefirsttimeyouaccessitaftereverymutationof.radius:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.radius
42.0
>>>circle.diameter#Withdelay
84.0
>>>circle.diameter#Withoutdelay
84.0
>>>circle.radius=100.0
>>>circle.diameter#Withdelay
200.0
>>>circle.diameter#Withoutdelay
200.0
Cool!Circleworkscorrectlynow!Itcomputesthediameterthefirsttimeyouaccessitandalsoeverytimeyouchangetheradius.
Anotheroptiontocreatecachedpropertiesistousefunctools.cached_property()fromthestandardlibrary.Thisfunctionworksasadecoratorthatallowsyoutotransformamethodintoacachedproperty.Thepropertycomputesitsvalueonlyonceandcachesitasanormalattributeduringthelifetimeoftheinstance:
#circle.py
fromfunctoolsimportcached_property
fromtimeimportsleep
classCircle:
def__init__(self,radius):
self.radius=radius
@cached_property
defdiameter(self):
sleep(0.5)#Simulateacostlycomputation
returnself.radius*2
Here,.diametercomputesandcachesitsvaluethefirsttimeyouaccessit.Thiskindofimplementationissuitableforthosecomputationsinwhichtheinputvaluesdon’tmutate.Here’showitworks:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.diameter#Withdelay
84.0
>>>circle.diameter#Withoutdelay
84.0
>>>circle.radius=100
>>>circle.diameter#Wrongdiameter
84.0
>>>#Allowdirectassignment
>>>circle.diameter=200
>>>circle.diameter#Cachedvalue
200
Whenyouaccess.diameter,yougetitscomputedvalue.Thatvalueremainsthesamefromthispointon.However,unlikeproperty(),cached_property()doesn’tblockattributemutationsunlessyouprovideapropersettermethod.That’swhyyoucanupdatethediameterto200inthelastcoupleoflines.
Ifyouwanttocreateacachedpropertythatdoesn’tallowmodification,thenyoucanuseproperty()andfunctools.cache()likeinthefollowingexample:
#circle.py
fromfunctoolsimportcache
fromtimeimportsleep
classCircle:
def__init__(self,radius):
self.radius=radius
@property
@cache
defdiameter(self):
sleep(0.5)#Simulateacostlycomputation
returnself.radius*2
Thiscodestacks@propertyontopof@cache.Thecombinationofbothdecoratorsbuildsacachedpropertythatpreventsmutations:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.diameter#Withdelay
84.0
>>>circle.diameter#Withoutdelay
84.0
>>>circle.radius=100
>>>circle.diameter
84.0
>>>circle.diameter=200
Traceback(mostrecentcalllast):
...
AttributeError:can'tsetattribute
Intheseexamples,whenyoutrytoassignanewvalueto.diameter,yougetanAttributeErrorbecausethesetterfunctionalitycomesfromtheinternaldescriptorofproperty.
RemoveadsLoggingAttributeAccessandMutation
Sometimesyouneedtokeeptrackofwhatyourcodedoesandhowyourprogramsflow.AwaytodothatinPythonistouselogging.Thismoduleprovidesallthefunctionalityyouwouldrequireforloggingyourcode.It’llallowyoutoconstantlywatchthecodeandgenerateusefulinformationabouthowitworks.
Ifyoueverneedtokeeptrackofhowandwhenyouaccessandmutateagivenattribute,thenyoucantakeadvantageofproperty()forthat,too:
#circle.py
importlogging
logging.basicConfig(
format="%(asctime)s:%(message)s",
level=logging.INFO,
datefmt="%H:%M:%S"
)
classCircle:
def__init__(self,radius):
self._msg='"radius"was%s.Currentvalue:%s'
self.radius=radius
@property
defradius(self):
"""Theradiusproperty."""
logging.info(self._msg%("accessed",str(self._radius)))
returnself._radius
@radius.setter
defradius(self,value):
try:
self._radius=float(value)
logging.info(self._msg%("mutated",str(self._radius)))
exceptValueError:
logging.info('validationerrorwhilemutating"radius"')
Here,youfirstimportlogginganddefineabasicconfiguration.ThenyouimplementCirclewithamanagedattribute.radius.Thegettermethodgeneratesloginformationeverytimeyouaccess.radiusinyourcode.Thesettermethodlogseachmutationthatyouperformon.radius.Italsologsthosesituationsinwhichyougetanerrorbecauseofbadinputdata.
Here’showyoucanuseCircleinyourcode:
>>>>>>fromcircleimportCircle
>>>circle=Circle(42.0)
>>>circle.radius
14:48:59:"radius"wasaccessed.Currentvalue:42.0
42.0
>>>circle.radius=100
14:49:15:"radius"wasmutated.Currentvalue:100
>>>circle.radius
14:49:24:"radius"wasaccessed.Currentvalue:100
100
>>>circle.radius="value"
15:04:51:validationerrorwhilemutating"radius"
Loggingusefuldatafromattributeaccessandmutationcanhelpyoudebugyourcode.Loggingcanalsohelpyouidentifysourcesofproblematicdatainput,analyzetheperformanceofyourcode,spotusagepatterns,andmore.
ManagingAttributeDeletion
Youcanalsocreatepropertiesthatimplementdeletingfunctionality.Thismightbearareusecaseofproperty(),buthavingawaytodeleteanattributecanbehandyinsomesituations.
Sayyou’reimplementingyourowntreedatatype.Atreeisanabstractdatatypethatstoreselementsinahierarchy.Thetreecomponentsarecommonlyknownasnodes.Eachnodeinatreehasaparentnode,exceptfortherootnode.Nodescanhavezeroormorechildren.
Nowsupposeyouneedtoprovideawaytodeleteorclearthelistofchildrenofagivennode.Here’sanexamplethatimplementsatreenodethatusesproperty()toprovidemostofitsfunctionality,includingtheabilitytoclearthelistofchildrenofthenodeathand:
#tree.py
classTreeNode:
def__init__(self,data):
self._data=data
self._children=[]
@property
defchildren(self):
returnself._children
@children.setter
defchildren(self,value):
ifisinstance(value,list):
self._children=value
else:
delself.children
self._children.append(value)
@children.deleter
defchildren(self):
self._children.clear()
def__repr__(self):
returnf'{self.__class__.__name__}("{self._data}")'
Inthisexample,TreeNoderepresentsanodeinyourcustomtreedatatype.EachnodestoresitschildreninaPythonlist.Thenyouimplement.childrenasapropertytomanagetheunderlyinglistofchildren.Thedeletermethodcalls.clear()onthelistofchildrentoremovethemall:
>>>>>>fromtreeimportTreeNode
>>>root=TreeNode("root")
>>>child1=TreeNode("child1")
>>>child2=TreeNode("child2")
>>>root.children=[child1,child2]
>>>root.children
[TreeNode("child1"),TreeNode("child2")]
>>>delroot.children
>>>root.children
[]
Here,youfirstcreatearootnodetostartpopulatingthetree.Thenyoucreatetwonewnodesandassignthemto.childrenusingalist.Thedelstatementtriggerstheinternaldeletermethodof.childrenandclearsthelist.
CreatingBackward-CompatibleClassAPIs
Asyoualreadyknow,propertiesturnmethodcallsintodirectattributelookups.ThisfeatureallowsyoutocreatecleanandPythonicAPIsforyourclasses.Youcanexposeyourattributespubliclywithouttheneedforgetterandsettermethods.
Ifyoueverneedtomodifyhowyoucomputeagivenpublicattribute,thenyoucanturnitintoaproperty.Propertiesmakeitpossibletoperformextraprocessing,suchasdatavalidation,withouthavingtomodifyyourpublicAPIs.
Supposeyou’recreatinganaccountingapplicationandyouneedabaseclasstomanagecurrencies.Tothisend,youcreateaCurrencyclassthatexposestwoattributes,.unitsand.cents:
classCurrency:
def__init__(self,units,cents):
self.units=units
self.cents=cents
#Currencyimplementation...
ThisclasslookscleanandPythonic.Nowsaythatyourrequirementschange,andyoudecidetostorethetotalnumberofcentsinsteadoftheunitsandcents.Removing.unitsand.centsfromyourpublicAPItousesomethinglike.total_centswouldbreakmorethanoneclient’scode.
Inthissituation,property()canbeanexcellentoptiontokeepyourcurrentAPIunchanged.Here’showyoucanworkaroundtheproblemandavoidbreakingyourclients’code:
#currency.py
CENTS_PER_UNIT=100
classCurrency:
def__init__(self,units,cents):
self._total_cents=units*CENTS_PER_UNIT+cents
@property
defunits(self):
returnself._total_cents//CENTS_PER_UNIT
@units.setter
defunits(self,value):
self._total_cents=self.cents+value*CENTS_PER_UNIT
@property
defcents(self):
returnself._total_cents%CENTS_PER_UNIT
@cents.setter
defcents(self,value):
self._total_cents=self.units*CENTS_PER_UNIT+value
#Currencyimplementation...
Nowyourclassstoresthetotalnumberofcentsinsteadofindependentunitsandcents.However,youruserscanstillaccessandmutate.unitsand.centsintheircodeandgetthesameresultasbefore.Goaheadandgiveitatry!
Whenyouwritesomethinguponwhichmanypeoplearegoingtobuild,youneedtoguaranteethatmodificationstotheinternalimplementationdon’taffecthowendusersworkwithyourclasses.
RemoveadsOverridingPropertiesinSubclasses
WhenyoucreatePythonclassesthatincludepropertiesandreleasetheminapackageorlibrary,youshouldexpectyouruserstodoalotofdifferentthingswiththem.Oneofthosethingscouldbesubclassingthemtocustomizetheirfunctionalities.Inthesecases,yourusershavetobecarefulandbeawareofasubtlegotcha.Ifyoupartiallyoverrideaproperty,thenyoulosethenon-overriddenfunctionality.
Forexample,supposeyou’recodinganEmployeeclasstomanageemployeeinformationinyourcompany’sinternalaccountingsystem.YoualreadyhaveaclasscalledPerson,andyouthinkaboutsubclassingittoreuseitsfunctionalities.
Personhasa.nameattributeimplementedasaproperty.Thecurrentimplementationof.namedoesn’tmeettherequirementofreturningthenameinuppercaseletters.Here’showyouendupsolvingthis:
#persons.py
classPerson:
def__init__(self,name):
self._name=name
@property
defname(self):
returnself._name
@name.setter
defname(self,value):
self._name=value
#Personimplementation...
classEmployee(Person):
@property
defname(self):
returnsuper().name.upper()
#Employeeimplementation...
InEmployee,youoverride.nametomakesurethatwhenyouaccesstheattribute,yougettheemployeenameinuppercase:
>>>>>>frompersonsimportEmployee,Person
>>>person=Person("John")
>>>person.name
'John'
>>>person.name="JohnDoe"
>>>person.name
'JohnDoe'
>>>employee=Employee("John")
>>>employee.name
'JOHN'
Great!Employeeworksasyouneed!Itreturnsthenameusinguppercaseletters.However,subsequenttestsuncoveranunexpectedbehavior:
>>>>>>employee.name="JohnDoe"
Traceback(mostrecentcalllast):
...
AttributeError:can'tsetattribute
Whathappened?Well,whenyouoverrideanexistingpropertyfromaparentclass,youoverridethewholefunctionalityofthatproperty.Inthisexample,youreimplementedthegettermethodonly.Becauseofthat,.namelosttherestofthefunctionalityfromthebaseclass.Youdon’thaveasettermethodanylonger.
Theideaisthatifyoueverneedtooverrideapropertyinasubclass,thenyoushouldprovideallthefunctionalityyouneedinthenewversionofthepropertyathand.
Conclusion
Apropertyisaspecialtypeofclassmemberthatprovidesfunctionalitythat’ssomewhereinbetweenregularattributesandmethods.PropertiesallowyoutomodifytheimplementationofinstanceattributeswithoutchangingthepublicAPIoftheclass.BeingabletokeepyourAPIsunchangedhelpsyouavoidbreakingcodeyouruserswroteontopofolderversionsofyourclasses.
PropertiesarethePythonicwaytocreatemanagedattributesinyourclasses.Theyhaveseveralusecasesinreal-worldprogramming,makingthemagreatadditiontoyourskillsetasaPythondeveloper.
Inthistutorial,youlearnedhowto:
CreatemanagedattributeswithPython’sproperty()
Performlazyattributeevaluationandprovidecomputedattributes
Avoidsetterandgettermethodswithproperties
Createread-only,read-write,andwrite-onlyattributes
Createconsistentandbackward-compatibleAPIsforyourclasses
Youalsowroteseveralpracticalexamplesthatwalkedyouthroughthemostcommonusecasesofproperty().Thoseexamplesincludeinputdatavalidation,computedattributes,loggingyourcode,andmore.
MarkasCompleted
🐍PythonTricks💌
Getashort&sweetPythonTrickdeliveredtoyourinboxeverycoupleofdays.Nospamever.Unsubscribeanytime.CuratedbytheRealPythonteam.
SendMePythonTricks»
AboutLeodanisPozoRamos
LeodanisisanindustrialengineerwholovesPythonandsoftwaredevelopment.He'saself-taughtPythondeveloperwith6+yearsofexperience.He'sanavidtechnicalwriterwithagrowingnumberofarticlespublishedonRealPythonandothersites.
»MoreaboutLeodanis
EachtutorialatRealPythoniscreatedbyateamofdeveloperssothatitmeetsourhighqualitystandards.Theteammemberswhoworkedonthistutorialare:
Aldren
Bartosz
Martin
Sadie
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:
best-practices
intermediate
python
KeepreadingReal Pythonbycreatingafreeaccountorsigning in:
Continue»
Alreadyhaveanaccount?Sign-In
—FREEEmailSeries—
🐍PythonTricks💌
GetPythonTricks»
🔒Nospam.Unsubscribeanytime.
AllTutorialTopics
advanced
api
basics
best-practices
community
databases
data-science
devops
django
docker
flask
front-end
gamedev
gui
intermediate
machine-learning
projects
python
testing
tools
web-dev
web-scraping
TableofContents
ManagingAttributesinYourClasses
TheGetterandSetterApproachinPython
ThePythonicApproach
GettingStartedWithPython’sproperty()
CreatingAttributesWithproperty()
Usingproperty()asaDecorator
ProvidingRead-OnlyAttributes
CreatingRead-WriteAttributes
ProvidingWrite-OnlyAttributes
PuttingPython’sproperty()IntoAction
ValidatingInputValues
ProvidingComputedAttributes
CachingComputedAttributes
LoggingAttributeAccessandMutation
ManagingAttributeDeletion
CreatingBackward-CompatibleClassAPIs
OverridingPropertiesinSubclasses
Conclusion
MarkasCompleted
Tweet
Share
Email
Almostthere!Completethisformandclickthebuttonbelowtogaininstantaccess:
×
5ThoughtsOnPythonMastery
StarttheClass»