作者:武沛齊
原文出處:http://www.cnblogs.com/wupeiqi/
概述
- 面向過程:根據(jù)業(yè)務(wù)邏輯從上到下寫壘代碼
- 函數(shù)式:將某功能代碼封裝到函數(shù)中,日后便無需重復(fù)編寫,僅調(diào)用函數(shù)即可
- 面向?qū)ο螅簩瘮?shù)進(jìn)行分類和封裝,讓開發(fā)“更快更好更強...”
面向過程編程最易被初學(xué)者接受棕兼,其往往用一長段代碼來實現(xiàn)指定功能,開發(fā)過程中最常見的操作就是粘貼復(fù)制垃杖,即:將之前實現(xiàn)的代碼塊復(fù)制到現(xiàn)需功能處胎许。
while True:
if cpu利用率 > 90%:
#發(fā)送郵件提醒
連接郵箱服務(wù)器
發(fā)送郵件
關(guān)閉連接
if 硬盤使用空間 > 90%:
#發(fā)送郵件提醒
連接郵箱服務(wù)器
發(fā)送郵件
關(guān)閉連接
if 內(nèi)存占用 > 80%:
#發(fā)送郵件提醒
連接郵箱服務(wù)器
發(fā)送郵件
關(guān)閉連接
隨著時間的推移,開始使用了函數(shù)式編程栏妖,增強代碼的重用性和可讀性乱豆,就變成了這樣:
def 發(fā)送郵件(內(nèi)容)
#發(fā)送郵件提醒
連接郵箱服務(wù)器
發(fā)送郵件
關(guān)閉連接
while True:
if cpu利用率 > 90%:
發(fā)送郵件('CPU報警')
if 硬盤使用空間 > 90%:
發(fā)送郵件('硬盤報警')
if 內(nèi)存占用 > 80%:
發(fā)送郵件('內(nèi)存報警')
今天我們來學(xué)習(xí)一種新的編程方式:面向?qū)ο缶幊蹋∣bject Oriented Programming,OOP底哥,面向?qū)ο蟪绦蛟O(shè)計)
注:Java和C#來說只支持面向?qū)ο缶幊塘埃鴓ython比較靈活即支持面向?qū)ο缶幊桃仓С趾瘮?shù)式編程。
創(chuàng)建類和對象
面向?qū)ο缶幊淌且环N編程方式趾徽,此編程方式的落地需要使用 “類” 和 “對象” 來實現(xiàn)续滋,所以,面向?qū)ο缶幊唐鋵嵕褪菍?“類” 和 “對象” 的使用孵奶。
類就是一個模板疲酌,模板里可以包含多個函數(shù),函數(shù)里實現(xiàn)一些功能。
對象則是根據(jù)模板創(chuàng)建的實例朗恳,通過實例對象可以執(zhí)行類中的函數(shù)湿颅。
- class是關(guān)鍵字,表示類
- 創(chuàng)建對象粥诫,類名稱后加括號即可
ps:類中的函數(shù)第一個參數(shù)必須是self(詳細(xì)見:類的三大特性之封裝)
類中定義的函數(shù)叫做 “方法”
# 創(chuàng)建類
class Foo:
def Bar(self):
print 'Bar'
def Hello(self, name):
print 'i am %s' %name
# 根據(jù)類Foo創(chuàng)建對象obj
obj = Foo()
obj.Bar() #執(zhí)行Bar方法
obj.Hello('wupeiqi') #執(zhí)行Hello方法
誒油航,你在這里是不是有疑問了?使用函數(shù)式編程和面向?qū)ο缶幊谭绞絹韴?zhí)行一個“方法”時函數(shù)要比面向?qū)ο蠛啽恪?/p>
- 面向?qū)ο螅骸緞?chuàng)建對象】【通過對象執(zhí)行方法】
- 函數(shù)編程:【執(zhí)行函數(shù)】
觀察上述對比答案則是肯定的怀浆,然后并非絕對谊囚,場景的不同適合其的編程方式也不同。
總結(jié):函數(shù)式的應(yīng)用場景 --> 各個函數(shù)之間是獨立且無共用的數(shù)據(jù)
面向?qū)ο笕筇匦?/h2>
面向?qū)ο蟮娜筇匦允侵福悍庋b执赡、繼承和多態(tài)镰踏。
一、封裝
封裝沙合,顧名思義就是將內(nèi)容封裝到某個地方奠伪,以后再去調(diào)用被封裝在某處的內(nèi)容。
所以首懈,在使用面向?qū)ο蟮姆庋b特性時绊率,需要:
- 將內(nèi)容封裝到某處
- 從某處調(diào)用被封裝的內(nèi)容
第一步:將內(nèi)容封裝到某處
self
是一個形式參數(shù),當(dāng)執(zhí)行 obj1 = Foo('wupeiqi', 18 )
時究履,self
等于 obj1
即舌;當(dāng)執(zhí)行 obj2 = Foo('alex', 78 )
時,self
等于 obj2
所以挎袜,內(nèi)容其實被封裝到了對象 obj1
和 obj2
中顽聂,每個對象中都有 name
和 age
屬性,在內(nèi)存里類似于下圖來保存盯仪。
第二步:從某處調(diào)用被封裝的內(nèi)容
調(diào)用被封裝的內(nèi)容時紊搪,有兩種情況:
- 通過對象直接調(diào)用
- 通過self間接調(diào)用
1、通過對象直接調(diào)用被封裝的內(nèi)容
上圖展示了對象 obj1
和 obj2
在內(nèi)存中保存的方式全景,根據(jù)保存格式可以如此調(diào)用被封裝的內(nèi)容:對象.屬性名
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
obj1 = Foo('wupeiqi', 18)
print obj1.name # 直接調(diào)用obj1對象的name屬性
print obj1.age # 直接調(diào)用obj1對象的age屬性
obj2 = Foo('alex', 73)
print obj2.name # 直接調(diào)用obj2對象的name屬性
print obj2.age # 直接調(diào)用obj2對象的age屬性
2耀石、通過self間接調(diào)用被封裝的內(nèi)容
執(zhí)行類中的方法時,需要通過self間接調(diào)用被封裝的內(nèi)容
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def detail(self):
print self.name
print self.age
obj1 = Foo('wupeiqi', 18)
obj1.detail() # Python默認(rèn)會將obj1傳給self參數(shù)爸黄,即:obj1.detail(obj1)滞伟,所以,此時方法內(nèi)部的 self = obj1炕贵,即:self.name 是 wupeiqi 梆奈;self.age 是 18
obj2 = Foo('alex', 73)
obj2.detail() # Python默認(rèn)會將obj2傳給self參數(shù),即:obj1.detail(obj2)称开,所以亩钟,此時方法內(nèi)部的 self = obj2乓梨,即:self.name 是 alex ; self.age 是 78
綜上所述清酥,對于面向?qū)ο蟮姆庋b來說扶镀,其實就是使用構(gòu)造方法將內(nèi)容封裝到 對象 中,然后通過對象直接或者self間接獲取被封裝的內(nèi)容焰轻。
練習(xí)一:在終端輸出如下信息
小明臭觉,10歲,男辱志,上山去砍柴
小明胧谈,10歲,男荸频,開車去東北
小明,10歲客冈,男旭从,最愛大保健
老李,90歲场仲,男和悦,上山去砍柴
老李,90歲渠缕,男鸽素,開車去東北
老李,90歲亦鳞,男馍忽,最愛大保健
老張...
函數(shù)式編程:
def kanchai(name, age, gender):
print "%s,%s歲,%s,上山去砍柴" %(name, age, gender)
def qudongbei(name, age, gender):
print "%s,%s歲,%s,開車去東北" %(name, age, gender)
def dabaojian(name, age, gender):
print "%s,%s歲,%s,最愛大保健" %(name, age, gender)
kanchai('小明', 10, '男')
qudongbei('小明', 10, '男')
dabaojian('小明', 10, '男')
kanchai('老李', 90, '男')
qudongbei('老李', 90, '男')
dabaojian('老李', 90, '男')
面向?qū)ο螅?/p>
class Foo:
def __init__(self, name, age ,gender):
self.name = name
self.age = age
self.gender = gender
def kanchai(self):
print "%s,%s歲,%s,上山去砍柴" %(self.name, self.age, self.gender)
def qudongbei(self):
print "%s,%s歲,%s,開車去東北" %(self.name, self.age, self.gender)
def dabaojian(self):
print "%s,%s歲,%s,最愛大保健" %(self.name, self.age, self.gender)
xiaoming = Foo('小明', 10, '男')
xiaoming.kanchai()
xiaoming.qudongbei()
xiaoming.dabaojian()
laoli = Foo('老李', 90, '男')
laoli.kanchai()
laoli.qudongbei()
laoli.dabaojian()
上述對比可以看出,如果使用函數(shù)式編程燕差,需要在每次執(zhí)行函數(shù)時傳入相同的參數(shù)遭笋,如果參數(shù)多的話,又需要粘貼復(fù)制了... 徒探;而對于面向?qū)ο笾恍枰趧?chuàng)建對象時瓦呼,將所有需要的參數(shù)封裝到當(dāng)前對象中,之后再次使用時测暗,通過self間接去當(dāng)前對象中取值即可央串。
練習(xí)二:游戲人生程序
1、創(chuàng)建三個游戲人物碗啄,分別是:
蒼井井质和,女,18稚字,初始戰(zhàn)斗力1000
東尼木木侦另,男,20,初始戰(zhàn)斗力1800
波多多褒傅,女弃锐,19,初始戰(zhàn)斗力2500
2殿托、游戲場景霹菊,分別:
草叢戰(zhàn)斗,消耗200戰(zhàn)斗力
自我修煉支竹,增長100戰(zhàn)斗力
多人游戲旋廷,消耗500戰(zhàn)斗力
游戲人生:
# -*- coding:utf-8 -*-
# ##################### 定義實現(xiàn)功能的類 #####################
class Person:
def __init__(self, na, gen, age, fig):
self.name = na
self.gender = gen
self.age = age
self.fight =fig
def grassland(self):
"""注釋:草叢戰(zhàn)斗,消耗200戰(zhàn)斗力"""
self.fight = self.fight - 200
def practice(self):
"""注釋:自我修煉礼搁,增長100戰(zhàn)斗力"""
self.fight = self.fight + 200
def incest(self):
"""注釋:多人游戲饶碘,消耗500戰(zhàn)斗力"""
self.fight = self.fight - 500
def detail(self):
"""注釋:當(dāng)前對象的詳細(xì)情況"""
temp = "姓名:%s ; 性別:%s ; 年齡:%s ; 戰(zhàn)斗力:%s" % (self.name, self.gender, self.age, self.fight)
print temp
# ##################### 開始游戲 #####################
cang = Person('蒼井井', '女', 18, 1000) # 創(chuàng)建蒼井井角色
dong = Person('東尼木木', '男', 20, 1800) # 創(chuàng)建東尼木木角色
bo = Person('波多多', '女', 19, 2500) # 創(chuàng)建波多多角色
cang.incest() #蒼井空參加一次多人游戲
dong.practice()#東尼木木自我修煉了一次
bo.grassland() #波多多參加一次草叢戰(zhàn)斗
#輸出當(dāng)前所有人的詳細(xì)情況
cang.detail()
dong.detail()
bo.detail()
cang.incest() #蒼井空又參加一次多人游戲
dong.incest() #東尼木木也參加了一個多人游戲
bo.practice() #波多多自我修煉了一次
#輸出當(dāng)前所有人的詳細(xì)情況
cang.detail()
dong.detail()
bo.detail()
二、繼承
繼承馒吴,面向?qū)ο笾械睦^承和現(xiàn)實生活中的繼承相同扎运,即:子可以繼承父的內(nèi)容。
例如:
貓可以:喵喵叫饮戳、吃豪治、喝、拉扯罐、撒
狗可以:汪汪叫负拟、吃、喝歹河、拉掩浙、撒
如果我們要分別為貓和狗創(chuàng)建一個類,那么就需要為 貓 和 狗 實現(xiàn)他們所有的功能秸歧,如下所示:
class 貓:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
上述代碼不難看出涣脚,吃、喝寥茫、拉遣蚀、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次纱耻。如果使用 繼承 的思想芭梯,如下實現(xiàn):
動物:吃、喝弄喘、拉玖喘、撒
貓:喵喵叫(貓繼承動物的功能)
狗:汪汪叫(狗繼承動物的功能)
偽代碼:
class 動物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在類后面括號中寫入另外一個類名,表示當(dāng)前類繼承另外一個類
class 貓(動物):
def 喵喵叫(self):
print '喵喵叫'
# 在類后面括號中寫入另外一個類名蘑志,表示當(dāng)前類繼承另外一個類
class 狗(動物):
def 汪汪叫(self):
print '喵喵叫'
代碼實例:
class Animal:
def eat(self):
print "%s 吃 " %self.name
def drink(self):
print "%s 喝 " %self.name
def shit(self):
print "%s 拉 " %self.name
def pee(self):
print "%s 撒 " %self.name
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '貓'
def cry(self):
print '喵喵叫'
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed = '狗'
def cry(self):
print '汪汪叫'
# ######### 執(zhí)行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
所以累奈,對于面向?qū)ο蟮睦^承來說贬派,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實現(xiàn)每個方法澎媒。
注:除了子類和父類的稱謂搞乏,你可能看到過 派生類 和 基類 ,他們與子類和父類只是叫法不同而已戒努。
學(xué)習(xí)了繼承的寫法之后请敦,我們用代碼來是上述阿貓阿狗的功能:
代碼實例:
class Animal:
def eat(self):
print "%s 吃 " %self.name
def drink(self):
print "%s 喝 " %self.name
def shit(self):
print "%s 拉 " %self.name
def pee(self):
print "%s 撒 " %self.name
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '貓'
def cry(self):
print '喵喵叫'
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed = '狗'
def cry(self):
print '汪汪叫'
# ######### 執(zhí)行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
那么問題又來了,多繼承呢储玫?
- 是否可以繼承多個類
- 如果繼承的多個類每個類中都定了相同的函數(shù)侍筛,那么那一個會被使用呢?
1撒穷、Python的類可以繼承多個類匣椰,Java和C#中則只能繼承一個類
2、Python的類如果繼承了多個類端礼,那么其尋找方法的方式有兩種禽笑,分別是:深度優(yōu)先和廣度優(yōu)先
- 當(dāng)類是經(jīng)典類時,多繼承情況下齐媒,會按照深度優(yōu)先方式查找
- 當(dāng)類是新式類時,多繼承情況下纷跛,會按照廣度優(yōu)先方式查找
經(jīng)典類和新式類喻括,從字面上可以看出一個老一個新,新的必然包含了跟多的功能贫奠,也是之后推薦的寫法唬血,從寫法上區(qū)分的話,如果 當(dāng)前類或者父類繼承了object類唤崭,那么該類便是新式類拷恨,否則便是經(jīng)典類。
經(jīng)典類多繼承:
class D:
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 執(zhí)行bar方法時
# 首先去A類中查找谢肾,如果A類中沒有腕侄,則繼續(xù)去B類中找,如果B類中么有芦疏,則繼續(xù)去D類中找冕杠,如果D類中么有,則繼續(xù)去C類中找酸茴,如果還是未找到分预,則報錯
# 所以,查找順序:A --> B --> D --> C
# 在上述查找bar方法的過程中薪捍,一旦找到笼痹,則尋找過程立即中斷配喳,便不會再繼續(xù)找了
a.bar()
新式類多繼承:
class D(object):
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 執(zhí)行bar方法時
# 首先去A類中查找,如果A類中沒有凳干,則繼續(xù)去B類中找晴裹,如果B類中么有,則繼續(xù)去C類中找纺座,如果C類中么有息拜,則繼續(xù)去D類中找,如果還是未找到净响,則報錯
# 所以少欺,查找順序:A --> B --> C --> D
# 在上述查找bar方法的過程中,一旦找到馋贤,則尋找過程立即中斷赞别,便不會再繼續(xù)找了
a.bar()
經(jīng)典類:首先去A
類中查找,如果A
類中沒有配乓,則繼續(xù)去B
類中找仿滔,如果B
類中么有,則繼續(xù)去D
類中找犹芹,如果D
類中么有崎页,則繼續(xù)去C
類中找,如果還是未找到腰埂,則報錯飒焦。
新式類:首先去A
類中查找,如果A
類中沒有屿笼,則繼續(xù)去B
類中找牺荠,如果B
類中么有,則繼續(xù)去C
類中找驴一,如果C
類中么有休雌,則繼續(xù)去D
類中找,如果還是未找到肝断,則報錯杈曲。
注意:在上述查找過程中,一旦找到胸懈,則尋找過程立即中斷鱼蝉,便不會再繼續(xù)找了
三、多態(tài)
Pyhon不支持多態(tài)并且也用不到多態(tài)箫荡,多態(tài)的概念是應(yīng)用于Java和C#這一類強類型語言中魁亦,而Python崇尚“鴨子類型”。
Python偽代碼實現(xiàn)Java或C#的多態(tài):
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
# 由于在Java或C#中定義函數(shù)參數(shù)時羔挡,必須指定參數(shù)的類型
# 為了讓Func函數(shù)既可以執(zhí)行S1對象的show方法洁奈,又可以執(zhí)行S2對象的show方法间唉,所以,定義了一個S1和S2類的父類
# 而實際傳入的參數(shù)是:S1對象和S2對象
def Func(F1 obj):
"""Func函數(shù)需要接收一個F1類型或者F1子類的類型"""
print obj.show()
s1_obj = S1()
Func(s1_obj) # 在Func函數(shù)中傳入S1類的對象 s1_obj利术,執(zhí)行 S1 的show方法呈野,結(jié)果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函數(shù)中傳入Ss類的對象 ss_obj,執(zhí)行 Ss 的show方法印叁,結(jié)果:S2.show
Python “鴨子類型”:
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
def Func(obj):
print obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
總結(jié)
以上就是本節(jié)對于面向?qū)ο蟪跫壷R的介紹被冒,總結(jié)如下:
- 面向?qū)ο笫且环N編程方式,此編程方式的實現(xiàn)是基于對 類 和 對象 的使用轮蜕;
- 類 是一個模板昨悼,模板中包裝了多個“函數(shù)”供使用;
- 對象跃洛,根據(jù)模板創(chuàng)建的實例(即:對象)率触,實例用于調(diào)用被包裝在類中的函數(shù);
- 面向?qū)ο笕筇匦裕悍庋b汇竭、繼承和多態(tài)葱蝗;
問答專區(qū)
問題一:什么樣的代碼才是面向?qū)ο螅?/strong>
答:從簡單來說,如果程序中的所有功能都是用 類 和 對象 來實現(xiàn)细燎,那么就是面向?qū)ο缶幊塘恕?/p>
問題二:函數(shù)式編程 和 面向?qū)ο?如何選擇两曼?分別在什么情況下使用?
答:須知:對于 C# 和 Java 程序員來說不存在這個問題玻驻,因為該兩門語言只支持面向?qū)ο缶幊蹋ú恢С趾瘮?shù)式編程)悼凑。而對于 Python 和 PHP 等語言卻同時支持兩種編程方式,且函數(shù)式編程能完成的操作击狮,面向?qū)ο蠖伎梢詫崿F(xiàn)佛析;而面向?qū)ο蟮哪芡瓿傻牟僮饕胬希瘮?shù)式編程不行(函數(shù)式編程無法實現(xiàn)面向?qū)ο蟮姆庋b功能)彪蓬。
所以,一般在Python開發(fā)中捺萌,全部使用面向?qū)ο?或 面向?qū)ο蠛秃瘮?shù)式混合使用
面向?qū)ο蟮膽?yīng)用場景:
1.多函數(shù)需使用共同的值档冬,如:數(shù)據(jù)庫的增、刪桃纯、改酷誓、查操作都需要連接數(shù)據(jù)庫字符串、主機名态坦、用戶名和密碼盐数。
Demo:
class SqlHelper:
def __init__(self, host, user, pwd):
self.host = host
self.user = user
self.pwd = pwd
def 增(self):
# 使用主機名、用戶名伞梯、密碼(self.host 玫氢、self.user 帚屉、self.pwd)打開數(shù)據(jù)庫連接
# do something
# 關(guān)閉數(shù)據(jù)庫連接
def 刪(self):
# 使用主機名、用戶名漾峡、密碼(self.host 攻旦、self.user 、self.pwd)打開數(shù)據(jù)庫連接
# do something
# 關(guān)閉數(shù)據(jù)庫連接
def 改(self):
# 使用主機名生逸、用戶名牢屋、密碼(self.host 、self.user 槽袄、self.pwd)打開數(shù)據(jù)庫連接
# do something
# 關(guān)閉數(shù)據(jù)庫連接
def 查(self):
# 使用主機名烙无、用戶名、密碼(self.host 掰伸、self.user 皱炉、self.pwd)打開數(shù)據(jù)庫連接
# do something
# 關(guān)閉數(shù)據(jù)庫連接# do something
2.需要創(chuàng)建多個事物,每個事物屬性個數(shù)相同狮鸭,但是值的需求合搅。
如:張三、李四歧蕉、楊五灾部,他們都有姓名、年齡惯退、血型赌髓,但其都是不相同。即:屬性個數(shù)相同催跪,但值不相同锁蠕。
Demo:
class Person:
def __init__(self, name ,age ,blood_type):
self.name = name
self.age = age
self.blood_type = blood_type
def detail(self):
temp = "i am %s, age %s , blood type %s " % (self.name, self.age, self.blood_type)
print temp
zhangsan = Person('張三', 18, 'A')
lisi = Person('李四', 73, 'AB')
yangwu = Person('楊五', 84, 'A')
問題三:類和對象在內(nèi)存中是如何保存?
答:類以及類中的方法在內(nèi)存中只有一份懊蒸,而根據(jù)類創(chuàng)建的每一個對象都在內(nèi)存中需要存一份荣倾,大致如下圖:
如上圖所示,根據(jù)類創(chuàng)建對象時骑丸,對象中除了封裝 name
和 age
的值之外舌仍,還會保存一個類對象指針,該值指向當(dāng)前對象的類通危。
當(dāng)通過 obj1
執(zhí)行 【方法一】 時铸豁,過程如下:
- 根據(jù)當(dāng)前對象中的 類對象指針 找到類中的方法
- 將對象
obj1
當(dāng)作參數(shù)傳給方法的第一個參數(shù)self
以上是本篇文章的全部內(nèi)容,下一篇《面向?qū)ο筮M(jìn)階篇》菊碟。