2022 Python全攻略:OOP - HackMD

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

OOP is a method of structring a program by bounding related properties and behaviors into individual objects. There are many built-in classes in Python, ...       Published LinkedwithGitHub Like Bookmark Subscribe Edit --- tags:Python --- #2022Python全攻略:OOP :::info :earth_asia:**前言** 本筆記是udemy"2022Python全攻略"課程的學習紀錄。

參考連結:https://www.udemy.com/course/python-master/ ::: ##CH6.Object-OrientedPrograming ###91.IntrotoOOP *OOPisamethodofstructringaprogrambyboundingrelatedpropertiesandbehaviorsintoindividualobjects. *Therearemanybuilt-inclassesinPython,andwecancreateourowntypesaswell. ***Aclassisacodetemplateforcreatingobjects.**Objectshavepropertiesandbehaviorassociatedwiththem. *Basically,wecanconsider"class"asanewdatatypethatcancreateonourown,justlikethebuilt-inPythondatatypes. *Thenamingconventionofclassnameisalwayscapitalizedthefirstletter! ```python= classRobot: pass myRobot1=Robot() print(type(myRobot1))# ``` *Inanobject-orientedapproach,the`__init__()`methodisthePythonequivalentoftheC++,Java,andJavaScriptconstructor. *The`__init__()`functioniscalledeverytimeanobjectiscreatedfromaclass. *The`__init__()`methodletstheclassinitializetheobject'sattributesandservesnootherpurpose.Itisonlyusedwithinclasses. *Amethodisafunctionthat"belongsto"anobject;themethod'sfirstparameterwillbecalled`self`(byconvention).Inotherprogramminglanguages,thiswordiscalled`this`. *Function第一個參數`self`指向的不是class,而是之後class衍生的物件(bychien-ming) ```python= classRobot: #inclasses,wecanalsodefineadocstring """CalssRobotisforcreatingrobots.""" #constructor def__init__(self,inputname,inputage): self.name=inputname self.age=inputage defwalk(self): print(f"{self.name}iswalking...") defsleep(self,inputtime): print(f"{self.name}willsleepfor{inputtime}seconds...") myRobot1=Robot("Wilson",25) myRobot2=Robot("Grace",42) print(myRobot1.__doc__) print(myRobot1.name) print(myRobot2.age) myRobot2.walk() myRobot2.sleep(20) ``` ###92.ClassAttribute,StaticMethodandClassMethod *A**classattribute**isaPythonvariablethatbelongstoaclassratherthanaparticularobject. *Itissharedbetweenalltheobjectsofthisclass,anditisdefinedoutsidetheconstructorfunction(Thisisjustlikethestaticattributeinotherprogramminglanguages.) *Classattributebelongstotheclasssothatwecangettheattributedirectlyfromtheclassitself. *Classattributescanbeaccessedbyeither: 1.`self.__class__.attribute`(insidemethoddefinition) 2.`objectname.attribute`(outsidemethoddefinition) 3.`classname.attribute`(tryNOTtousethisinmethoddefinition) *Insideamethod,whenreferringtotheclassitself,trytouse`self.__class__`asmuchaspossiable.Avoidhardcodetheclassname. ```python= classRobot: ingredient="metal" def__init__(self,inputname,inputage): self.name=inputname self.age=inputage defShowAttr(self): print(f"Iammadeof{self.__class__.ingredient}.") myRobot1=Robot("Wilson",25) myRobot1.ShowAttr() print(myRobot1.ingredient) print(Robot.ingredient)#盡量別用這個,萬一class要改名會不好維護 ``` *Similarly,a**staticmetod**isalsoamethodthatisboundtotheclassandnottheobjectoftheclass;however,astaticmethoddeosn'tneedselftobeitsfirstparameter. *Pythonalsohasanothermethodcalledthe**classmethod**,similartothestaticmethod.However,whendefiningclassmethod,wegive`cls`(standsforclass)asthemethod'sfirstparameter. *What'sthebenefitofusingtheclassmethodoverthestaticmethod?Well,ifweeverchangetheclassname,thenthehard-codedclassnameinsidethefunctionwillnotworkanymore.Wewillhavetomanuallychangealltheclassnameinsidethemethod'scodeifwehardcodeclassname. ```python= classCircle: """Thisclasscreatescircle.""" pi=3.14159 all_circles=[] def__init__(self,radius): self.radius=radius self.__class__.all_circles.append(self) defarea(self): returnself.__class__.pi*(self.radius**2) @staticmethod deftotal_area(): total=0 forcircleinCircle.all_circles:#此處Circle一旦class改名就要手動跟著改,不好維護 total+=circle.area() returntotal @classmethod deftotal_area2(cls): total=0 forcircleincls.all_circles: total+=circle.area() returntotal c1=Circle(1) c2=Circle(15) c3=Circle(19) print(c1.__class__.total_area()) print(c1.__class__.total_area2()) ``` ###93.QuickNote 有學生提出一個有趣的問題,所以我想要很快解釋一下: 上述的code一樣可以用一般method呈現 兩者在速度上,或可讀性上有無特殊差別或是說業界以那個方法使用占較多? 答: 其實很多classmethod都可以寫成一般的method。

但classmethod的好處在於,classmethod屬於這個class,所以我們透過class來instantiateobjects時,每個object佔用的記憶體會更少。

假定某個class有10個method以及3個classmethod,那麼我們instantiate100個objects時,會用到的記憶體量是100x10=1000單位。

但如果我們不用classmethod,全寫成一般的method,那麼我們instantiate100個objects時,會用到的記憶體量是100x13=1300單位。

因此,如果我們知道某個method可以寫成classmethod,就應該要這麼做,節省記憶體用量。

另外,對程式節省記憶體是很重要的。

例如,GoogleChrome瀏覽器因為佔用太多記憶體而為人詬病,edge,firefox,safari等等瀏覽器的用戶比例近年不斷攀升,就是因為這個緣故。

###94.Inheritance(繼承) *Inheritanceallowsustodefineaclassthatinheritsallthemethodsandpropertiesfromanotherclass. *The**parentclass**istheclassbeinginheritedfrom,alsocalledthebaseclass. *The**childclass**istheclassthatinheritsfromanotherclass,alsocalledthederivedclass. ```python= classPeople: def__init__(self,name,age): self.name=name self.age=age defsleep(self): print(f"{self.name}issleeping...") defwalk(self): print(f"{self.name}iswalking...") classStudent(People): def__init__(self,name,age,student_id): People.__init__(self,name,age)#呼叫父類的方法 self.student_id=student_id defwalk(self):#會複寫父類的函式 print(f"Student{self.name}iswalking...") student1=Student("Wilson",25,123456789) print(student1.student_id) student1.sleep() student1.walk() ``` *The`super()`(命名源自離散數學的superset)methodreturnsaproxyobject(temporaryobjectofthesuperclass)thatallowsustoaccessmethodsofthebaseclass. ```python= classPeople: def__init__(self,name,age): self.name=name self.age=age defsleep(self): print(f"{self.name}issleeping...") defwalk(self): print(f"{self.name}iswalking...") classStudent(People): def__init__(self,name,age,student_id): super().__init__(name,age)#差在這行,呼叫父類的方法 self.student_id=student_id defwalk(self):#會複寫父類的函式 print(f"Student{self.name}iswalking...") student1=Student("Wilson",25,123456789) print(student1.student_id) student1.sleep() student1.walk() ``` ###95.MultipleInheritance *InJavaandJavascript,multipleinheritancesarenotallowed.InC++,multipleinheritancesarepermitted,butit'sincrediblycomplicated;peopleavoidusingitasmuchaspossible. *Pythonsupportsmultipleinheritances!Thisisnotspecial.Thatjustmeanswecaninheritfrommorethanoneparentclass. ```python= classC: defdo_stuff(self): print("hellofromclassC") classD: defdo_another_stuff(self): print("hellofromclassD") classA(C,D): pass a=A() a.do_stuff() a.do_another_stuff() ``` *Thingsbecomecomplicatediftheinheritancegraphishuge.Forexample,ifwehave(每個綠圈圈代表一個class): ![](https://i.imgur.com/1jWk3LB.png) ```python= classE: defdo_stuff_XXX(self): print("doingstufffromclassE") classF: defdo_stuff_XXX(self): print("doingstufffromclassF") classB(E,F): defdo_stuff_B(self): print("doingstufffromclassB") classC: defdo_stuff_C(self): print("doingstufffromclassC") classG: defdo_stuff_XXX(self): print("doingstufffromclassG") classD(G): defdo_stuff_D(self): print("doingstufffromclassD") classA(B,C,D): defdo_stuff_A(self): print("doingstufffromclassA") a=A() a.do_stuff_C()#doingstufffromclassC a.do_stuff_XXX()#doingstufffromclassE(why???) print(A.mro())#用classname,而非objectname print(A.__mro__) ``` *Thebasicprincipleof**methodresolutionorder(MRO)**usesa**depth-firstgraphtraversalalgorithm**. *Byapplyingthealgorithm,wewouldget:A,B,E,F,C,D,G.Thisistheorderofwhereamethodshouldbelookedup. *IfyoucannotdeterminedtheMRO,wecanusethePythonbuilt-intofigureouttheorder: *`classname.mro()`returnsalist *`classname.__mro__`returnsatuple *WhydoweusethiskindofalgorithmtodoMRO?Well,thealgorithmjustmakessurethateachclassisvisitedonce.However,thislogicdoesn'tmakeyourcodeeasiertoreadormaintain. *Somepeopleinsistthatmultipleinheritancesarebadidea.Manytaskscanbedonewithoutmultipleinheritancesaswell.However,wedogetsomebenefitsbyusingthis-itallowsustoavoidbuildingprofound(很深的)inheritancerelations.(如果不用多重繼承,就要一個一個class連續繼承,關係鏈會變得很深(?)) ###96.C3Linearization *C3linearization演算法專門用來處理多重繼承 *Traditionally,**diamondproblem**occurswhenwedomultipleinheritance.Considerthis: ![](https://i.imgur.com/Q7NxcuL.png) *Byapplyingdepthfirsttraversal,weknowthatthemethodresolutionorderwillbe:D,B,A,C,A.However,wearegettingduplicateAintheorder;it'sveryintuitive(直覺的)thatAcomesbeforeC.InthepreviousversionofPython,MROisinthisorder.Python3usesanewalgorithmcalledthe**C3LinearizationAlgorithm**. *Wewon'tspendtimelearningwhatthisis(sincethisiswayoutofthescopeofoutcourse);however,thisC3linearizationdoessolvebothproblems;wedon'thaveduplicateAandCcomesbeforeA. *WithC3linearization,theMROofthediamondproblemwillbe:D,B,C,A. *ReadaboutC3linearizationfrom[here](https://en.wikipedia.org/wiki/C3_linearization)ifinterested. ###97.Private(私有)AttributesandMethods *Inthecontextofclass,privatemeanstheattributesormethodsareonlyavailablefortheclassmembers,notfortheoutsideoftheclass. *Whendefiningprivateattributesandmethods,wejustneedtoaddadoubleunderscore(底線)atthefrontandnounderscoreattheend;then,thatattributeormethodwouldbecomeprivate. ```python= classRobot: def__init__(self,name): self.name=name self.__age=25 def__private_method(self): print("Hellofromprivatemethod.") defgreet(self): self.__private_method() defget_age(self): print(f"Hi,I'm{self.__age}yearsold.") defset_age(self,inputage): self.__age=inputage myRobot1=Robot("Wilson") myRobot1.greet()#間接去呼叫私有方法 print(myRobot1.__age)#AttributeError:'Robot'objecthasnoattribute'__age' myRobot1.set_age(65)#用setter間接去設定私有屬性 myRobot1.get_age()#用getter間接去取得私有屬性 ``` *InPython,byconvention,anyattributeormethodstartedwithasingleunderscoreisalsoseenasprivate. *However,eventhoughwecanaccesstheattributedirectly,weshouldstillconsideritasaprivatevariable;trynottoaccessitasmuchaspossiable. ####OOPStyleinPython *Traditionally,OOPdesignemphasizesinformationhiding;thisisknownas"encapsulation(封裝)".Allattributesofanobjectshouldbeasprivateaspossiable.Thepurposeofencapsulationistopreventchangingattributesofanobjectasthatmightcauseanunexpectederror. *Whenwereallyhavetoaccessattributesofanobject,weactuallydefinepublicmethodscalledgetterandsetter.Traditionally,gettersandsettersaccessattributesanddosomemiddlewarework,suchaschangingunitsorcheckingthemaxandminvalue. *適當的封裝,可以將物件使用介面的程式實作部份隱藏起來,不讓使用者看到,同時確保使用者無法任意更改物件內部的重要資料,若想接觸資料只能通過公開接入方法(Publiclyaccessiblemethods)的方式(如:"getter"和"setter")。

封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性,但也要注意不要過度封裝。

```python= classRobot: def__init__(self,name): self.name=name self.__age=25 defget_age(self): print(f"Hi,I'm{self.__age}yearsold.") defset_age(self,inputage): ifinputage>0andinputage<100: self.__age=inputage else: print("Invalidinputvalue.") myRobot1=Robot("Wilson") myRobot1.set_age(-10) ``` *However,inPython,getterandsetterarenotconsideredPythonic! *Then,wemighthaveaquestion:howdowepreventoutsidersfromrandomlyacccessingandchangingtheinnerattributesofobjects?InthePythoncommunity,afamoussayingis,"Weareallconsentingadults.".Thatmeanswetrusteachother;ifsomeonewantstomakechaos,theyhavetoberesponsiblefortheirdecision.That'swhyweseebothsingleanddoubleunderscorecanbeusedforprivatedattributes.(就算singleunderscore不是正式語法也沒關係,大家有建立默契就好) *ThedifferentbetweenPythonOOPandtraditionalOOPhighlightstheconceptofPythonandotherporgramminglanguages.TraditionalOOPemphasizeserrorpreventionandimformationhiding;ontheotherhand,Pythonfocusesonreadability. ###98.@propertydecorator *之後才會介紹什麼是decorator(透過裝飾器機制可以在定義函式與方法後,用簡單的方式來做修改並重新定義),此處先介紹其中的@property:可以在一個class中新增一個虛擬property *Sometimes,whenweaccessingorassigninganattributeofanobject,wedon'twanttoaccessorassignitdirectly. *Forexample, *WhenwehavetochangetheunitfromCelsiustoFahrenheit. *Whenweneedtocheckifthevalueassignmentiswithinacertainrange. *Traditionally,thisisdonebygettersandsettersinotherprogramminglanuage.InPython,@propertydecoratorsisabuilt-indecorator,whichishelpfulindefiningvirtualpropertieseffortlessly. ```python= classEmployee: def__init__(self): self.income=0 self.__tax=0#privatevariable defearn_money(self,money): self.income+=money self.__tax=self.income*0.05#課程影片用"+=",應該是"="? defget_tax(self): returnself.__tax wilson=Employee() wilson.earn_money(300) print(wilson.get_tax())#15 wilson.earn_money(500) print(wilson.get_tax())#40 ``` 上述程式中的line4`self.__tax=0`其實可以透過@property簡化語法 ```python= classEmployee: def__init__(self): self.income=0 defearn_money(self,money): self.income+=money @property deftax_amount(self):#雖然定義成function,但其實是Employee的(虛擬)property returnself.income*0.05 wilson=Employee() wilson.earn_money(300) print(wilson.tax_amount)#15 wilson.earn_money(500) print(wilson.tax_amount)#40 ``` 用另一個叫做@method.setter的decorator,可以幫虛擬property建立一個虛擬setter,用來設定值 ```python= classEmployee: def__init__(self): self.income=0 defearn_money(self,money): self.income+=money @property deftax_amount(self): returnself.income*0.05 @tax_amount.setter deftax_amount(self,tax_number): self.income=tax_number*20#由稅金回推原先賺了多少 wilson=Employee() wilson.tax_amount=200 print(wilson.income)#4000 ``` ###99.TheMighty(強大的)HashFunction *Ahash(雜湊)isafunctionthatconvertonvaluetoanother. *InPython,there'sabuilt-inhash()function,anditreturnsafixed-sizedinteger. *Hashabledatatypescanbehashedbyhash():string,int,float,bool,tuple(withhashableelements),None. *MostofPython'simmutablebuilt-inobjectsarehashable;mutablecontainers(suchaslistsordictionaries)arenot;immutablecontainers(suchastuples)areonlyhashableiftheirelementsareallhashable. *Objectswhichareinstancesofuserdefinedclassesarehashablebydefault.(butwecandefinethehashvaluebyimplementingthe`__hash__()`method) *Set,listanddictionaryarenothashable. ```python= print(hash("Howareyoudoingtoday?")) print(hash(10)) print(hash(9.999)) print(hash(True)) print(hash((1,4,7,11))) print(hash(None)) #result:(為啥每次執行後都不一樣?見後面) #3497754365099253586 #10 #2303537166204481545 #1 #943204701236206362 #-9223363241360645427 ``` *Thetwoideas,hashableandimmutable(不可變的),arerelatedbecauseobjectswhichareusedashashkeysmusttypicallybeimmutable,sotheirhashvaluedoesn'tchange. *Ifitwereallowedtochange,thenthelocationofthatobjectinadatastrcturesuchasahashtablewouldchange,andthenthewholepurposeofhashingforefficiencyisdefeated. **Review:Common-useddatatypes** |Name|Type|Mutable| |------------|-----|:-------:| |Integer|int|no| |Float|float|no| |String|str|no| |Boolean|bool|no| |List|list|yes| |Dictionaries|dict|yes| |Tuples|tup|no| |Sets|set|yes| *Herearesomekeyfeaturesofagoodhashfunction: 1.Consistent(一致):eachtimewegivethesameinputtothehash()function,weneedtogetthesameoutput. 2.DistributedEvenly(均勻分布):asmallchangeininputshouldresultinahugedifferenceinoutput.Thiswillreducethenumberofhashcollisions(雜湊碰撞,兩個不同的輸入得到相同的hash結果). 3.Notinvertible(不可逆):thisfunctionshouldnotbeinvertibleforsecuritypurpose. *Wewon'tlearnhowtocreateourownhashfunctioninthiscourse.Ifinterested,youcanlearnmoreabouthashfunctioninalgorithmanddatastructureclass. *NotethatthehashofavalueonlyneedstobethesameforonerunofPython.InPython3.3,resultofhash()willchangeforeverynewrun.Thisistomakeithardertoguesswhathashvalueaparticularstringwillhave,whichisanimportantsecurityfeatureforapplication. *Hashvaluesof`hash()`shouldthereforenotbestoredpermanently.Ifweneedtousehashvaluesinapermanentway,wecantakealookatthemore"serious"typesofhashes-cryptographichashfunctions(密碼雜湊函式),suchasbcrypt(常用於網頁後端加密),SHA-256(用於比特幣),RSAalgorithm,EllipticCurveCryptography. *Theideaofhashfunctioniswidelyusedintherealworld. *Passwordsarehashedbeforetheygetstoredinthedatabase.Bythat,evensomeonehacksintothesystem,theycannotseethepassword.Also,theycannotdoanyreverseengineering. *Hashtable(一種資料結構)useshashfunction.InPython,dictionaryandsetbothimplementhashtable.Therefore,whendoingvaluelook-up(byusingthemembershipoperator`in`)indictionaryandset,thespeedisconstantlyfastregardlessofthesizeofdictionaryorset.(However,list'slook-uptimedependsonthesizeofthelist.) ###100.\_\_hash\_\_and\_\_eq\_\_ *`__hash__()`跟`__eq__()`前者用於定義對物件做hash()的結果,後者定義兩個物件做比較時的結果,定義的內容跟返回值都需要自訂 *Anyobjectthatimplementsthe`__hash__()`functioncanbeusedasakeyfordictionary.Aneasy,correctwaytoimplement`__hash__()`istouseakeytuple.Wecanjustreturnthehashedvalueofthekey. ```python= classRobot: def__init__(self,name,age,address): self.name=name self.age=age self.address=address #defineaprivatemethodkey() def__key(self): return(self.name,self.age,self.address) #implement__hash__()function def__hash__(self): #returnhash(self.__key())#此返回值為任何Robot生成的物件去做hash會得到的值 return1000#測試一下,正常要用上面那句 robot1=Robot("Wilson",25,"Taiwan") robot2=Robot("Will",46,"Korea") print(hash(robot1))#1000 print(hash(robot2))#1000 ``` *Also,Pythonautomaticallycallsthe`__eq__()`methodofaclasswhenyouseethe`==`operatortocomparetheinstancesoftheclass. *Therefore,inourclass,weshouldimplementthis`__eq__()`methodby: 1.Checkifthetypematches 2.Checkifthekey()matches ```python= classRobot: def__init__(self,name,age,address): self.name=name self.age=age self.address=address #defineaprivatemethodkey() def__key(self): return(self.name,self.age,self.address) #implement__hash__()function def__hash__(self): returnhash(self.__key())#此返回值為任何Robot生成的物件去做hash會得到的值 def__eq__(self,other):#用'other'代表被比較者 ifisinstance(other,Robot):#先用isinstance()檢查other是不是類別為Robot的物件 returnother.__key()==self.__key()#再比較__key()的內容 #此返回值為任何Robot生成的物件去做比較時會得到的值:True表示typematches且key()matches returnNotImplemented#Python內建常數,表示沒有對其他類型做實現 robot1=Robot("Wilson",25,"Taiwan") robot2=Robot("Will",46,"Korea") robot3=Robot("Wilson",25,"Taiwan") print(robot1==robot2)#False print(robot1==robot3)#True,表示這兩個物件是同個class生出來的 ``` ###101.DunderorMagicMethods *DunderormagicmethodsinPythonarethemethodshavongtwoprefixsandsuffixunderscoresinthemethodname.Thesearecommonlyusedfor**operatoroverloading(運算子重載,Python中允許同一運算符根據上下文具有不同含義)**. *Dunderheremeans"doubleunderscores". *Besidesthe`__init__`,`__eq__`,`__hash__`welearned,thefollowinglistissomedundermethodswecanimplementinPythoncalss: *`__len__` *`__str__`:user-readablestring *`__repr__`:standsforrepresentation *`__add__` *`__gt__`:greatthan *`__ge__`:greatthanorequalto *`__lt__`:lessthan *`__le__`:lessthanorequalto *`__str__()`isusedforcreatingoutputforenduserwhile`__repr__()`ismainlyusedfordebugginganddevelopment. *`__repr__()`goalistobeunambiguous(明白的)and`__str__()`istobereadable. *ThePythonbuilt-inprint()methodlooksforthestringmethodoftheobject. ```python= classRobot: def__init__(self,name,age,address): self.name=name self.age=age self.address=address def__len__(self): return100 def__str__(self): return"someinformation" def__repr__(self): return"somedebugmessage" def__add__(self,other): return400 def__gt__(self,other): return500 def__ge__(self,other): return600 def__lt__(self,other): return700 def__le__(self,other): return800 robot1=Robot("Wilson",25,"Taiwan") robot2=Robot("Will",46,"Korea") print(len(robot1)) print(str(robot1))#或print(robot1) print(repr(robot1)) print(robot1+robot2) print(robot1>robot2) print(robot1>=robot2) print(robot1



請為這篇文章評分?