带你学python基础:面向对象编程
面向?qū)ο缶幊淌莻€(gè)啥呢,其實(shí),在傳統(tǒng)的語言中,比如 C 語言,是不存在面向?qū)ο缶幊踢@個(gè)概念的,那時(shí)候的語言只有面向過程編程,也就是我們寫代碼從頭寫到底,最多也就是有函數(shù)。所以,這樣的代碼風(fēng)格是比較難維護(hù)的。
后來,隨著編程語言的改進(jìn),在很多的語言都有了面向?qū)ο蟮乃枷?#xff0c;比如 C++、Java、C#等,而 Python也是如此。
一、那什么是面向?qū)ο竽?#xff1f;
拿個(gè)簡單的例子說說,比如我們一個(gè)人,有頭、身體、腿、手等,這些東西在面向?qū)ο蟮乃枷胫?#xff0c;都可以把他們拆分為一個(gè)一個(gè)的對(duì)象,而不會(huì)把人就看做一個(gè)對(duì)象。
在我們經(jīng)常玩的游戲中,每一個(gè)英雄,每一個(gè)兵器,都是一個(gè)個(gè)的對(duì)象,每個(gè)事物都是對(duì)象。
在面向?qū)ο缶幊讨?#xff0c;我們還會(huì)談到另外一個(gè)概念:類。
那么什么是類呢,類是一個(gè)抽象的概念,我們知道有動(dòng)物,動(dòng)物下面有各種各樣不同的動(dòng)物,狗,老虎等。所以,類是就是動(dòng)物,也就是不同類型的動(dòng)物的總稱,也是抽象。而對(duì)象就是具體的類別的動(dòng)物。
類是對(duì)象的類型,具有相同屬性和行為事物的統(tǒng)稱。類是抽象的,在使用的時(shí)候通常會(huì)找到這個(gè)類的一個(gè)具體存在。
萬物皆對(duì)象,對(duì)象擁有自己的特征和行為。
打個(gè)比方,我們每個(gè)人都可以看做是一個(gè)對(duì)象,而我們每個(gè)人都有我們自己的不同的特征,同時(shí),我們也會(huì)產(chǎn)生我們的各種各樣的行為。
這個(gè)圖是不是看了就知道對(duì)象的特性了。
相信講了這么多了,我們應(yīng)該知道什么是類和對(duì)象了。下面我們講一下,如何定義類。
二、定義類
首先,我們通過一個(gè)案例來說說如何定義類,最后再給出定義類的方法。
# 定義類 class pen():def __init__(self, str, len):self.str = strself.len = len # 實(shí)例變量通過init初始化聲明# 定義類變量width = 5'''獲取信息'''def getStr(self):print('str:%s,len:%s' % (self.str, self.len))print('width:', pen.width)pen = pen('初始化', 10) pen.getStr()上面定義了一個(gè)類,這個(gè)類名為pen,然后,我們?cè)陬愔卸x了它的特征屬性str和len,同時(shí),我們還定義了一個(gè)行為(獲取信息)。
通過這個(gè)例子,我們就可以看出怎么定義類的。
定義類規(guī)則
class 類名:屬性列表方法列表在上面這個(gè)例子中,我們發(fā)現(xiàn)這里存在兩種變量,一種是實(shí)例屬性,一種是類屬性。下面我們就說說這兩種變量有什么區(qū)別。
- 類變量:也可以說類屬性,類變量在整個(gè)實(shí)例化的對(duì)象中是公用的。類變量定義在類中且在函數(shù)體之外。類變量通常不作為實(shí)例變量使用。如果需要用在函數(shù)中使用類名.類屬性訪問,如例子中的width = 5。
- 實(shí)例變量:也可以說實(shí)例屬性,定義在方法中的變量,只作用于當(dāng)前實(shí)例的類, 如例子中的len。
好了,我們知道怎么定義類和定義類變量和實(shí)例變量,那么如何訪問這些變量呢?
三、訪問變量
方法
實(shí)例對(duì)象.屬性舉例
例如,我們需要訪問上面的pen的變量,則可以使用下面的方式。
當(dāng)然,你可能會(huì)想,還有其他方式嗎,確實(shí),還有其他方式,Python也提供了類似JavaScript的訪問方式。
getattr(obj, name[, default]) #訪問對(duì)象的屬性 hasattr(obj,name) # 檢查是否存在一個(gè)屬性 setattr(obj,name,value) # 設(shè)置一個(gè)屬性。如果屬性不存在,會(huì)創(chuàng)建一個(gè)新屬性 delattr(obj, name) # 刪除屬性舉例
# -*- coding:utf-8 -*-# 定義類 class pen():def __init__(self, str, len):self.str = strself.len = len # 實(shí)例變量通過init初始化聲明# 定義類變量width = 5'''獲取信息'''def getStr(self):print('str:%s,len:%s' % (self.str, self.len))print('width:', pen.width)pen = pen('初始化', 10)# 通過內(nèi)置方法訪問屬性 print(getattr(pen, 'len')) print(hasattr(pen, 'len'))setattr(pen, 'len', 20) print(pen.len)delattr(pen, 'len') print(pen.len)內(nèi)置類屬性
另外,Python本身還提供了自己內(nèi)置的類屬性,分別有下面這些。
__dict__ : 類的屬性(包含一個(gè)字典,由類的屬性名:值組成) 實(shí)例化類名.__dict__ __doc__ :類的文檔字符串 (類名.) 實(shí)例化類名.__doc__ __name__: 類名,實(shí)現(xiàn)方式 類名.__name__ __bases__ : 類的所有父類構(gòu)成元素(包含了以個(gè)由所有父類組成的元組)舉例
我們還是以上面的例子來講
特殊說明
在前面的例子中,我們看到了init和self這兩個(gè)關(guān)鍵字,下面講解一下。
__init__():是一個(gè)特殊的方法屬于類的專有方法,被稱為類的構(gòu)造函數(shù)或初始化方法,方法的前面和后面都有兩個(gè)下劃線。
這是為了避免Python默認(rèn)方法和普通方法發(fā)生名稱的沖突。每當(dāng)創(chuàng)建類的實(shí)例化對(duì)象的時(shí)候,__init__()方法都會(huì)默認(rèn)被運(yùn)行。作用就是初始化已實(shí)例化后的對(duì)象,這就是構(gòu)造函數(shù)的意思。
在方法定義中,第一個(gè)參數(shù)self是必不可少的。類的方法和普通的函數(shù)的區(qū)別就是self,self并不是Python的關(guān)鍵字,你完全可以用其他單詞取代他,只是按照慣例和標(biāo)準(zhǔn)的規(guī)定,推薦使用self。
既然是面向?qū)ο缶幊?#xff0c;那么,接下來肯定要說一下面向?qū)ο蟮娜筇匦粤恕?/p>
四、面向?qū)ο蟮娜筇匦?/h3>
封裝
封裝這個(gè)特性,其實(shí)在前面就已經(jīng)接觸到了,只是沒有明白的說而已。
封裝字面上的意思就是把東西包裹起來,那么,在面向?qū)ο蟮木幊讨?#xff0c;其實(shí)封裝也就是這個(gè)意思,常見的,比如,前面我們說的的類 class,我們把一些對(duì)象的屬性和行為包裹在一個(gè)類里面,這就是封裝的特性。
繼承
我們都知道,我們有父子關(guān)系,很多時(shí)候,兒子都會(huì)去繼承父親的財(cái)產(chǎn)的,好像都是這樣的吧,哈哈。
在面向?qū)ο蟮木幊讨幸彩沁@么個(gè)意思,但是不叫父親和兒子,我們把父親叫做父類,兒子稱為子類,我們子類去繼承父類的財(cái)產(chǎn),這個(gè)就是一個(gè)繼承的特性。
那我們?nèi)绾斡?Python 來表達(dá)這種關(guān)系呢,下面,我們用一個(gè)例子先講一下,后面再講規(guī)則。
父親,兒子和女兒的故事
# 定義類 class Father():'''定義一個(gè)父親類'''def __init__(self, money, house):self.money = moneyself.house = housedef wealth(self):print('父親給我 %d w, %d 套房子' % (self.money, self.house))class Son(Father):'''定義一個(gè)兒子類,繼承自父親類'''def __init__(self, money, house):super().__init__(money, house)class Daughter(Father):'''定義一個(gè)女兒類,繼承自父親類'''def __init__(self, money, house):super().__init__(money, house)# 上面定義了一個(gè)父親類,一個(gè)兒子類,一個(gè)女兒類。 # 兒子類和女兒類都繼承自父親類,所以,他們就擁有了父親的財(cái),在編程中也就是擁有了變量和方法。 son = Son(100, 10) daughter = Daughter(200, 5)# 繼承自父親類,所以自己不定義這個(gè)wealth方法,也會(huì)自動(dòng)繼承這個(gè)方法 son.wealth() daughter.wealth()通過這個(gè)例子,定義了一個(gè)父親類,一個(gè)兒子類,一個(gè)女兒類。
兒子類和女兒類都繼承自父親類,所以,他們就擁有了父親的財(cái),在編程中也就是擁有了變量和方法。
在上面的例子中發(fā)現(xiàn),我們只要在定義類的時(shí)候,在括號(hào)()中寫上父類的名字,這就是繼承了。
其中,我們也要注意,一個(gè) super() 方法,它的作用是用來繼承父類的屬性。
所以,下面我們就大概知道繼承的規(guī)則怎么寫了。
規(guī)則
class DerivedClassName(Base1, Base2, Base3):<statement-1>...<statement-N>注意:圓括號(hào)中父類的順序,如果繼承的父類中有相同的方法名,而在子類中使用時(shí)未指定,python將從左至右查找父類中是否包含方法,在圓括號(hào)中有多個(gè)類名時(shí),我們稱為:多繼承。也就是說,我們從多個(gè)父類繼承了。
好了,繼承我們就說到這里了。下面我們?cè)僬f說最后一個(gè)面向?qū)ο蟮奶匦?#xff1a;多態(tài)!
多態(tài)
在談到多態(tài)時(shí),我們就不得不提到另外一個(gè)概念了,這個(gè)概念就是重寫。例如,我們的容貌有一些地方是會(huì)跟父母很相像的,但是,我們也會(huì)有很多我們自己的特點(diǎn)。在面向?qū)ο缶幊汤锩嬉彩沁@樣的,我們會(huì)繼承父類的特性,但是,在繼承的同時(shí),我們也會(huì)發(fā)生改變的,這就是重寫。
那么如何實(shí)現(xiàn)重寫呢?接著看!
# 定義類 class Father():'''定義一個(gè)父親類'''def __init__(self, money, house):self.money = moneyself.house = housedef wealth(self):print('父親給我 %d w, %d 套房子' % (self.money, self.house))def character(self):print('我很高!')class Son(Father):'''定義一個(gè)兒子類,繼承自父親類'''def __init__(self, money, house):super().__init__(money, house)def character(self):print('我很胖!')class Daughter(Father):'''定義一個(gè)女兒類,繼承自父親類'''def __init__(self, money, house):super().__init__(money, house)def character(self):print('我很瘦!')# 上面定義了一個(gè)父親類,一個(gè)兒子類,一個(gè)女兒類。 # 兒子類和女兒類都繼承自父親類,所以,他們就擁有了父親的財(cái),在編程中也就是擁有了變量和方法。 father = Father(350, 20) son = Son(100, 10) daughter = Daughter(200, 5)# 繼承自父親類,所以自己不定義這個(gè)wealth方法,也會(huì)自動(dòng)繼承這個(gè)方法 son.wealth() daughter.wealth()# 多態(tài) father.character() son.character() daughter.character()在繼承的那個(gè)例子的基礎(chǔ)上,我們又在父親類中加了一個(gè) character 方法,然后兒子類和女兒類在繼承這個(gè)方法的同時(shí),還對(duì)這個(gè) character 方法進(jìn)行了修改。
所以,此時(shí),兒子類和女兒類在調(diào)用這兩個(gè)方法時(shí),顯示的內(nèi)容就不一樣了。
也就是說,當(dāng)子類和父類都存在相同的 character()方法時(shí),子類的 character() 覆蓋了父類的 character(),在代碼運(yùn)行時(shí),會(huì)調(diào)用子類的 character()。
這樣,我們就獲得了繼承的另一個(gè)好處:多態(tài)。
多態(tài)的好處就是,當(dāng)我們需要傳入更多的子類,例如新增 Teenagers、Adult 等時(shí),我們只需要繼承 Father 類型就可以了,而 character()方法既可以直不重寫(即使用Father的),也可以重寫一個(gè)特有的。這就是多態(tài)的意思。調(diào)用方只管調(diào)用,不管細(xì)節(jié),而當(dāng)我們新增一種Father的子類時(shí),只要確保新方法編寫正確,而不用管原來的代碼。這就是著名的“開閉”原則:
- 對(duì)擴(kuò)展開放(Open for extension):允許子類重寫方法函數(shù)
- 對(duì)修改封閉(Closed for modification):不重寫,直接繼承父類方法函數(shù)
ok,面向?qū)ο蟮娜筇匦跃椭v到這里。接下來講講關(guān)于類的其他知識(shí)!
五、類屬性與實(shí)例屬性
我們?cè)谇懊娴膸讉€(gè)例子中,我們發(fā)現(xiàn),在類中我們都定義了一些屬性或者說變量。那什么是類屬性,什么是實(shí)例屬性呢?
類屬性是類本身的屬性,該類的實(shí)例都能調(diào)用,而實(shí)例屬性是某個(gè)具體的實(shí)例特有的屬性,不會(huì)影響到類,也不會(huì)影響到其他實(shí)例。
1.實(shí)例屬性
關(guān)于實(shí)例屬性,我們只要記住下面三點(diǎn)就可以了。
- 在__init__(self,...)中初始化
- 內(nèi)部調(diào)用時(shí)都需要加上self.
- 外部調(diào)用時(shí)用對(duì)象名.屬性名調(diào)用
2.類屬性
- 在內(nèi)部用類名.類屬性名調(diào)用
- 外部既可以用類名.類屬性名,又可以用對(duì)象名.類屬性名來調(diào)用,但是,后者是實(shí)例屬性的值。
下面我們看一個(gè)簡單例子:
# 定義類 class Father():'''定義一個(gè)父親類'''age = 40 # 定義一個(gè)類屬性def __init__(self, money, house):self.money = money # 實(shí)例屬性self.house = housedef wealth(self):print('父親給我 %d w, %d 套房子,類屬性: %d ,實(shí)例屬性:%d' % (self.money, self.house, Father.age, self.age))def character(self):print('我很高!')father = Father(350, 20)father.house = 4 # 修改實(shí)例變量的值,對(duì)象名.屬性 Father.age = 41 # 修改類變量的值:類名.屬性 或 對(duì)象名.屬性(但后者修改的值是實(shí)例屬性) father.age = 42 father.wealth()下面,我們?cè)儆?strong>類屬性實(shí)現(xiàn)一個(gè)數(shù)值自增
# 定義類 class Father():'''定義一個(gè)父親類'''age = 40 # 定義一個(gè)類屬性def __init__(self, money, house):self.money = money # 實(shí)例屬性self.house = houseFather.age += 1 # 類屬性自增father = Father(350, 20) print(Father.age)father2 = Father(350, 20) print(Father.age)但是,在類中,我們不能用 self.age 來自增,這樣是不行的,就像在類外不能用對(duì)象名.類變量來改變值一樣。
下面,我們用self.age 來自增試試什么效果。
# -*- coding:utf-8 -*-# 定義類 class Father():'''定義一個(gè)父親類'''age = 40 # 定義一個(gè)類屬性def __init__(self, money, house):self.money = money # 實(shí)例屬性self.house = houseself.age += 1 # 類屬性自增father = Father(350, 20) print(Father.age)father2 = Father(350, 20) print(Father.age)這段代碼就將 Father 改為 self。但輸出結(jié)果卻不改變,如下
接下來,我們?cè)偕钊氲姆治鲆幌?#xff0c;看類屬性和實(shí)例屬性到底有什么聯(lián)系?
# 定義類 class Father():'''定義一個(gè)父親類'''age = 40 # 定義一個(gè)類屬性def __init__(self, money, house):self.money = money # 實(shí)例屬性self.house = housefather1 = Father(350, 20) father2 = Father(350, 20)father1.age += 1 print(father1.age, father2.age, Father.age) Father.age += 1 print(father1.age, father2.age, Father.age) # father2.age 因?yàn)檫@個(gè)實(shí)例屬性不存在,所以找類屬性為41 Father.age += 1 print(father1.age, father2.age, Father.age)從這個(gè)結(jié)果我們可以看出,類屬性自增,實(shí)例屬性是不會(huì)跟著自增的,實(shí)例屬性自增,每個(gè)實(shí)例屬性之間也是獨(dú)立的,但是,當(dāng)實(shí)例屬性不存在時(shí),編譯器是會(huì)去找類屬性的值。
所以說,在Python中屬性的查找機(jī)制是自下而上的,即首先在實(shí)例屬性中查找,如果實(shí)例屬性不存在,再到類屬性中查找。
六、訪問權(quán)限(來自:https://www.cnblogs.com/Lambda721/p/6130213.html)
在Class內(nèi)部,可以有屬性和方法,而外部代碼可以通過直接調(diào)用實(shí)例變量的方法來操作數(shù)據(jù),這樣,就隱藏了內(nèi)部的復(fù)雜邏輯。
但是,從前面Student類的定義來看,外部代碼還是可以自由地修改一個(gè)實(shí)例的name、score屬性:
>>> bart = Student('Bart Simpson', 98) >>> bart.score 98 >>> bart.score = 59 >>> bart.score 59如果要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個(gè)下劃線__,在Python中,實(shí)例的變量名如果以__開頭,就變成了一個(gè)私有變量(private),只有內(nèi)部可以訪問,外部不能訪問,所以,我們把Student類改一改:
class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scoredef print_score(self):print('%s: %s' % (self.__name, self.__score))改完后,對(duì)于外部代碼來說,沒什么變動(dòng),但是已經(jīng)無法從外部訪問實(shí)例變量.__name和實(shí)例變量.__score了:
>>> bart = Student('Bart Simpson', 98) >>> bart.__name Traceback (most recent call last):File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'這樣就確保了外部代碼不能隨意修改對(duì)象內(nèi)部的狀態(tài),這樣通過訪問限制的保護(hù),代碼更加健壯。
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加get_name和get_score這樣的方法:
class Student(object):...def get_name(self):return self.__namedef get_score(self):return self.__score如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:
class Student(object):...def set_score(self, score):self.__score = score你也許會(huì)問,原先那種直接通過bart.score = 59也可以修改啊,為什么要定義一個(gè)方法大費(fèi)周折?因?yàn)樵诜椒ㄖ?#xff0c;可以對(duì)參數(shù)做檢查,避免傳入無效的參數(shù):
class Student(object):...def set_score(self, score):if 0 <= score <= 100:self.__score = scoreelse:raise ValueError('bad score')需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下劃線開頭,并且以雙下劃線結(jié)尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。
有些時(shí)候,你會(huì)看到以一個(gè)下劃線開頭的實(shí)例變量名,比如_name,這樣的實(shí)例變量外部是可以訪問的,但是,按照約定俗成的規(guī)定,當(dāng)你看到這樣的變量時(shí),意思就是,“雖然我可以被訪問,但是,請(qǐng)把我視為私有變量,不要隨意訪問”。
雙下劃線開頭的實(shí)例變量是不是一定不能從外部訪問呢?其實(shí)也不是。不能直接訪問__name是因?yàn)镻ython解釋器對(duì)外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:
>>> bart._Student__name 'Bart Simpson'但是強(qiáng)烈建議你不要這么干,因?yàn)椴煌姹镜腜ython解釋器可能會(huì)把__name改成不同的變量名。
總的來說就是,Python本身沒有任何機(jī)制阻止你干壞事,一切全靠自覺。
最后注意下面的這種錯(cuò)誤寫法:
>>> bart = Student('Bart Simpson', 98) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 設(shè)置__name變量! >>> bart.__name 'New Name'表面上看,外部代碼“成功”地設(shè)置了__name變量,但實(shí)際上這個(gè)__name變量和class內(nèi)部的__name變量不是一個(gè)變量!內(nèi)部的__name變量已經(jīng)被Python解釋器自動(dòng)改成了_Student__name,而外部代碼給bart新增了一個(gè)__name變量。不信試試:
>>> bart.get_name() # get_name()內(nèi)部返回self.__name 'Bart Simpson'例子:
#!/usr/bin/env python3 # -*- coding: utf-8 -*-class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scoredef get_name(self):return self.__namedef get_score(self):return self.__scoredef set_score(self, score):if 0 <= score <= 100:self.__score = scoreelse:raise ValueError('bad score')def get_grade(self):if self.__score >= 90:return 'A'elif self.__score >= 60:return 'B'else:return 'C'bart = Student('Bart Simpson', 59) print('bart.get_name() =', bart.get_name()) bart.set_score(60) print('bart.get_score() =', bart.get_score())print('DO NOT use bart._Student__name:', bart._Student__name)終于到最后一個(gè)知識(shí)點(diǎn)了,這篇文章寫的真的久!
七、類方法與靜態(tài)方法
這個(gè)知識(shí)點(diǎn)就說說,因?yàn)闆]什么好說的。
普通方法我們都知道怎么定義了。
1.普通方法
def fun_name(self,...):pass2.靜態(tài)方法
- 通過裝飾器 @staticmethod 裝飾
- 不能訪問實(shí)例屬性
- 參數(shù)不能傳入self
- 與類相關(guān)但是不依賴類與實(shí)例的方法
3.類方法
- 通過裝飾 @classmethod 修飾
- 不能訪問實(shí)例屬性
- 參數(shù)必須傳入cls
必須傳入cls參數(shù)(此類對(duì)象和self代表實(shí)例對(duì)象),并且用此來調(diào)用類屬性:cls.類屬性名。
最后,再總結(jié)一下。
- 靜態(tài)方法與類方法都可以通過類或者實(shí)例來調(diào)用,其兩個(gè)的特點(diǎn)都是不能夠調(diào)用實(shí)例屬性。
- 靜態(tài)方法不需要接收參數(shù),使用類名.類屬性。
下面,再舉一個(gè)例子看看。
# -*- coding:utf-8 -*-# 定義類 class Father():'''定義一個(gè)父親類'''age = 40 # 定義一個(gè)類屬性def __init__(self, money, house):self.money = money # 實(shí)例屬性self.house = house# 創(chuàng)建普通方法def getMoney(self):# 類屬性的使用通過類名.屬性名使用 這是規(guī)范# 私有屬性在類里面使用正常使用print('我有:%d w' % (self.money)) # 在方法里面使用實(shí)例屬性# 創(chuàng)建一個(gè)靜態(tài)方法@staticmethoddef aa(): # 不需要傳遞實(shí)例# 靜態(tài)方法不能訪問實(shí)例屬性# 靜態(tài)方法只能訪問類屬性print('我:%d 歲' % Father.age) # 在方法里面使用實(shí)例屬性# 類方法@classmethoddef bb(cls, n): # class 也不是關(guān)鍵字# 類方法不能訪問實(shí)例屬性cls.age = nprint('我:%d 歲' % cls.age) # 就用cls.類屬性father1 = Father(350, 20) father2 = Father(350, 20)# 通過對(duì)象來調(diào)用靜態(tài)方 father1.aa() # 通過對(duì)象來調(diào)用類方法 father1.bb(18)# 靜態(tài)方法和類方法的調(diào)用,推薦使用類名的方式去調(diào)用 # 通過類名來調(diào)用靜態(tài)方法 Father.aa() # 通過類名來調(diào)用類方法 Father.bb(18)八、總結(jié)
這一節(jié)講了很多,需要好好消化。
總結(jié)
以上是生活随笔為你收集整理的带你学python基础:面向对象编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 带你学python基础:函数是个func
- 下一篇: 带你学python基础:模块和包