面向?qū)ο缶幊淌莻€啥呢卜录,其實,在傳統(tǒng)的語言中眶明,比如 C 語言艰毒,是不存在面向?qū)ο缶幊踢@個概念的,那時候的語言只有面向過程編程赘来,也就是我們寫代碼從頭寫到底现喳,最多也就是有函數(shù)凯傲。所以,這樣的代碼風(fēng)格是比較難維護的嗦篱。
后來冰单,隨著編程語言的改進,在很多的語言都有了面向?qū)ο蟮乃枷刖拇伲热?C++诫欠、Java、C#等浴栽,而 Python也是如此荒叼。
一、那什么是面向?qū)ο竽兀?/h3>
拿個簡單的例子說說典鸡,比如我們一個人被廓,有頭、身體萝玷、腿嫁乘、手等,這些東西在面向?qū)ο蟮乃枷胫星虻铮伎梢园阉麄儾鸱譃橐粋€一個的對象蜓斧,而不會把人就看做一個對象。
在我們經(jīng)常玩的游戲中睁冬,每一個英雄挎春,每一個兵器,都是一個個的對象豆拨,每個事物都是對象直奋。
在面向?qū)ο缶幊讨校覀冞€會談到另外一個概念:類
施禾。
那么什么是類呢帮碰,類是一個抽象的概念,我們知道有動物拾积,動物下面有各種各樣不同的動物殉挽,狗,老虎等拓巧。所以斯碌,類
是就是動物,也就是不同類型的動物的總稱肛度,也是抽象傻唾。而對象
就是具體的類別的動物。
類是對象的類型,具有相同屬性和行為事物的統(tǒng)稱冠骄。類是抽象的伪煤,在使用的時候通常會找到這個類的一個具體存在。
萬物皆對象凛辣,對象擁有自己的特征和行為抱既。
打個比方,我們每個人都可以看做是一個對象扁誓,而我們每個人都有我們自己的不同的特征防泵,同時,我們也會產(chǎn)生我們的各種各樣的行為蝗敢。
這個圖是不是看了就知道對象的特性了捷泞。
相信講了這么多了,我們應(yīng)該知道什么是類和對象了寿谴。下面我們講一下锁右,如何定義類。
二讶泰、定義類
首先骡湖,我們通過一個案例來說說如何定義類,最后再給出定義類的方法峻厚。
# 定義類
class pen():
def __init__(self, str, len):
self.str = str
self.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
。 - 實例變量:也可以說實例屬性承疲,定義在方法中的變量邻耕,只作用于當(dāng)前實例的類鸥咖, 如例子中的
len
。
好了兄世,我們知道怎么定義類和定義類變量和實例變量啼辣,那么如何訪問這些變量呢?
三碘饼、訪問變量
方法
實例對象.屬性
舉例
例如熙兔,我們需要訪問上面的pen的變量,則可以使用下面的方式艾恼。
# 訪問變量
print(pen.len)
print(pen.width)
print(pen.str)
當(dāng)然住涉,你可能會想,還有其他方式嗎钠绍,確實舆声,還有其他方式,Python也提供了類似JavaScript的訪問方式柳爽。
getattr(obj, name[, default]) #訪問對象的屬性
hasattr(obj,name) # 檢查是否存在一個屬性
setattr(obj,name,value) # 設(shè)置一個屬性媳握。如果屬性不存在,會創(chuàng)建一個新屬性
delattr(obj, name) # 刪除屬性
舉例
# -*- coding:utf-8 -*-
# 定義類
class pen():
def __init__(self, str, len):
self.str = str
self.len = len # 實例變量通過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__ : 類的屬性(包含一個字典赵誓,由類的屬性名:值組成) 實例化類名.__dict__
__doc__ :類的文檔字符串 (類名.) 實例化類名.__doc__
__name__: 類名,實現(xiàn)方式 類名.__name__
__bases__ : 類的所有父類構(gòu)成元素(包含了以個由所有父類組成的元組)
舉例
我們還是以上面的例子來講
print(pen.__dict__) #會將實例對象的屬性和值通過字典的形式返回
print(pen.__doc__)
特殊說明
在前面的例子中打毛,我們看到了init
和self
這兩個關(guān)鍵字,下面講解一下俩功。
__init__()
:是一個特殊的方法屬于類的專有方法幻枉,被稱為類的構(gòu)造函數(shù)或初始化方法,方法的前面和后面都有兩個下劃線。
這是為了避免Python默認方法和普通方法發(fā)生名稱的沖突诡蜓。每當(dāng)創(chuàng)建類的實例化對象的時候熬甫,__init__()
方法都會默認被運行。作用就是初始化已實例化后的對象蔓罚,這就是構(gòu)造函數(shù)的意思椿肩。
在方法定義中,第一個參數(shù)self
是必不可少的豺谈。類的方法和普通的函數(shù)的區(qū)別就是self覆旱,self并不是Python的關(guān)鍵字,你完全可以用其他單詞取代他核无,只是按照慣例和標準的規(guī)定扣唱,推薦使用self
。
既然是面向?qū)ο缶幊蹋敲丛肷常酉聛砜隙ㄒf一下面向?qū)ο蟮娜筇匦粤恕?/p>
四炼彪、面向?qū)ο蟮娜筇匦?/h3>
封裝
封裝這個特性,其實在前面就已經(jīng)接觸到了正歼,只是沒有明白的說而已辐马。
封裝字面上的意思就是把東西包裹起來,那么局义,在面向?qū)ο蟮木幊讨邢惨鋵嵎庋b也就是這個意思,常見的萄唇,比如檩帐,前面我們說的的類 class,我們把一些對象的屬性和行為包裹在一個類里面另萤,這就是封裝的特性
湃密。
繼承
我們都知道,我們有父子關(guān)系四敞,很多時候泛源,兒子都會去繼承父親的財產(chǎn)的,好像都是這樣的吧忿危,哈哈达箍。
在面向?qū)ο蟮木幊讨幸彩沁@么個意思,但是不叫父親和兒子铺厨,我們把父親叫做父類缎玫,兒子稱為子類,我們子類去繼承父類的財產(chǎn)努释,這個就是一個繼承的特性
。
那我們?nèi)绾斡?Python 來表達這種關(guān)系呢咬摇,下面伐蒂,我們用一個例子先講一下,后面再講規(guī)則肛鹏。
父親逸邦,兒子和女兒的故事
# 定義類
class Father():
'''
定義一個父親類
'''
def __init__(self, money, house):
self.money = money
self.house = house
def 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將從左至右查找父類中是否包含方法椿争,在圓括號中有多個類名時,我們稱為:多繼承熟嫩。也就是說秦踪,我們從多個父類繼承了。
好了掸茅,繼承我們就說到這里了椅邓。下面我們再說說最后一個面向?qū)ο蟮奶匦裕?strong>多態(tài)!
多態(tài)
在談到多態(tài)時昧狮,我們就不得不提到另外一個概念了景馁,這個概念就是重寫。例如逗鸣,我們的容貌有一些地方是會跟父母很相像的合住,但是绰精,我們也會有很多我們自己的特點。在面向?qū)ο缶幊汤锩嬉彩沁@樣的聊疲,我們會繼承父類的特性茬底,但是,在繼承的同時获洲,我們也會發(fā)生改變的阱表,這就是重寫。
那么如何實現(xiàn)重寫呢贡珊?接著看最爬!
# 定義類
class Father():
'''
定義一個父親類
'''
def __init__(self, money, house):
self.money = money
self.house = house
def 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()
在繼承的那個例子的基礎(chǔ)上,我們又在父親類中加了一個 character
方法拳锚,然后兒子類和女兒類在繼承這個方法的同時假栓,還對這個 character
方法進行了修改。
所以霍掺,此時匾荆,兒子類和女兒類在調(diào)用這兩個方法時,顯示的內(nèi)容就不一樣了抗楔。
也就是說棋凳,當(dāng)子類和父類都存在相同的 character()
方法時拦坠,子類的 character()
覆蓋了父類的 character()
连躏,在代碼運行時,會調(diào)用子類的 character()
贞滨。
這樣入热,我們就獲得了繼承的另一個好處:多態(tài)拍棕。
多態(tài)的好處就是,當(dāng)我們需要傳入更多的子類勺良,例如新增 Teenagers绰播、Adult 等時,我們只需要繼承 Father 類型就可以了尚困,而 character()方法既可以直不重寫(即使用Father的)蠢箩,也可以重寫一個特有的。這就是多態(tài)的意思事甜。調(diào)用方只管調(diào)用谬泌,不管細節(jié),而當(dāng)我們新增一種Father的子類時逻谦,只要確保新方法編寫正確掌实,而不用管原來的代碼。這就是著名的“開閉”原則:
- 對擴展開放(Open for extension):允許子類重寫方法函數(shù)
- 對修改封閉(Closed for modification):不重寫邦马,直接繼承父類方法函數(shù)
ok贱鼻,面向?qū)ο蟮娜筇匦跃椭v到這里。接下來講講關(guān)于類的其他知識滋将!
五邻悬、類屬性與實例屬性
我們在前面的幾個例子中,我們發(fā)現(xiàn)耕渴,在類中我們都定義了一些屬性或者說變量拘悦。那什么是類屬性,什么是實例屬性呢橱脸?
類屬性是類本身的屬性础米,該類的實例都能調(diào)用,而實例屬性是某個具體的實例特有的屬性添诉,不會影響到類屁桑,也不會影響到其他實例。
1.實例屬性
關(guān)于實例屬性栏赴,我們只要記住下面三點就可以了蘑斧。
- 在
__init__(self,...)
中初始化 - 內(nèi)部調(diào)用時都需要加上self.
- 外部調(diào)用時用
對象名.屬性名
調(diào)用
2.類屬性
- 在內(nèi)部用
類名.類屬性名
調(diào)用 -
外部既可以用
類名.類屬性名
,又可以用對象名.類屬性名
來調(diào)用须眷,但是竖瘾,后者是實例屬性的值。
下面我們看一個簡單例子:
# 定義類
class Father():
'''
定義一個父親類
'''
age = 40 # 定義一個類屬性
def __init__(self, money, house):
self.money = money # 實例屬性
self.house = house
def 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 = house
Father.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 = house
self.age += 1 # 類屬性自增
father = Father(350, 20)
print(Father.age)
father2 = Father(350, 20)
print(Father.age)
這段代碼就將 Father
改為 self
扛禽。但輸出結(jié)果卻不改變气筋,如下
接下來,我們再深入的分析一下旋圆,看類屬性和實例屬性到底有什么聯(lián)系宠默?
# 定義類
class Father():
'''
定義一個父親類
'''
age = 40 # 定義一個類屬性
def __init__(self, money, house):
self.money = money # 實例屬性
self.house = house
father1 = 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)
從這個結(jié)果我們可以看出灵巧,類屬性自增搀矫,實例屬性是不會跟著自增的,實例屬性自增刻肄,每個實例屬性之間也是獨立的瓤球,但是,當(dāng)實例屬性不存在時敏弃,編譯器是會去找類屬性的值卦羡。
所以說,在Python中屬性的查找機制
是自下而上的麦到,即首先在實例屬性中查找绿饵,如果實例屬性不存在,再到類屬性中查找瓶颠。
六拟赊、訪問權(quán)限(來自:https://www.cnblogs.com/Lambda721/p/6130213.html)
在Class內(nèi)部,可以有屬性和方法粹淋,而外部代碼可以通過直接調(diào)用實例變量的方法來操作數(shù)據(jù)吸祟,這樣,就隱藏了內(nèi)部的復(fù)雜邏輯桃移。
但是屋匕,從前面Student類的定義來看,外部代碼還是可以自由地修改一個實例的name
借杰、score
屬性:
>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59
如果要讓內(nèi)部屬性不被外部訪問过吻,可以把屬性的名稱前加上兩個下劃線__
,在Python中第步,實例的變量名如果以__
開頭疮装,就變成了一個私有變量(private),只有內(nèi)部可以訪問粘都,外部不能訪問廓推,所以,我們把Student類改一改:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def 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'
這樣就確保了外部代碼不能隨意修改對象內(nèi)部的狀態(tài)堆生,這樣通過訪問限制的保護专缠,代碼更加健壯。
但是如果外部代碼要獲取name和score怎么辦淑仆?可以給Student類增加get_name
和get_score
這樣的方法:
class Student(object):
...
def get_name(self):
return self.__name
def 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 = score
else:
raise ValueError('bad score')
需要注意的是咒精,在Python中八堡,變量名類似__xxx__
的,也就是以雙下劃線開頭引矩,并且以雙下劃線結(jié)尾的,是特殊變量侵浸,特殊變量是可以直接訪問的旺韭,不是private變量,所以掏觉,不能用__name__
茂翔、__score__
這樣的變量名。
有些時候履腋,你會看到以一個下劃線開頭的實例變量名珊燎,比如_name
,這樣的實例變量外部是可以訪問的遵湖,但是悔政,按照約定俗成的規(guī)定,當(dāng)你看到這樣的變量時延旧,意思就是谋国,“雖然我可以被訪問,但是迁沫,請把我視為私有變量芦瘾,不要隨意訪問”捌蚊。
雙下劃線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是近弟。不能直接訪問__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' # 設(shè)置__name變量!
>>> bart.__name
'New Name'
表面上看订讼,外部代碼“成功”地設(shè)置了__name
變量髓窜,但實際上這個__name
變量和class內(nèi)部的__name
變量不是一個變量!內(nèi)部的__name
變量已經(jīng)被Python解釋器自動改成了_Student__name
欺殿,而外部代碼給bart
新增了一個__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 = name
self.__score = score
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
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,...):
pass
2.靜態(tài)方法
- 通過裝飾器
@staticmethod
裝飾 - 不能訪問實例屬性
- 參數(shù)不能傳入
self
- 與類相關(guān)但是不依賴類與實例的方法
3.類方法
- 通過裝飾
@classmethod
修飾 - 不能訪問實例屬性
- 參數(shù)必須傳入cls
必須傳入cls參數(shù)(此類對象和self代表實例對象),并且用此來調(diào)用類屬性:cls.類屬性名蜒谤。
最后山宾,再總結(jié)一下。
- 靜態(tài)方法與類方法都可以通過類或者實例來調(diào)用鳍徽,其兩個的特點都是不能夠調(diào)用實例屬性资锰。
- 靜態(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)方法
@staticmethod
def aa(): # 不需要傳遞實例
# 靜態(tài)方法不能訪問實例屬性
# 靜態(tài)方法只能訪問類屬性
print('我:%d 歲' % Father.age) # 在方法里面使用實例屬性
# 類方法
@classmethod
def bb(cls, n): # class 也不是關(guān)鍵字
# 類方法不能訪問實例屬性
cls.age = n
print('我:%d 歲' % cls.age) # 就用cls.類屬性
father1 = Father(350, 20)
father2 = Father(350, 20)
# 通過對象來調(diào)用靜態(tài)方
father1.aa()
# 通過對象來調(diào)用類方法
father1.bb(18)
# 靜態(tài)方法和類方法的調(diào)用,推薦使用類名的方式去調(diào)用
# 通過類名來調(diào)用靜態(tài)方法
Father.aa()
# 通過類名來調(diào)用類方法
Father.bb(18)
八濒募、總結(jié)
這一節(jié)講了很多鞭盟,需要好好消化。