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