51CTO同步發(fā)布地址:http://3060674.blog.51cto.com/3050674/1689163
面向過(guò)程編程最易被初學(xué)者接受,其往往用一長(zhǎng)段代碼來(lái)實(shí)現(xiàn)指定功能,開(kāi)發(fā)過(guò)程中最常見(jiàn)的操作就是粘貼復(fù)制,即:將之前實(shí)現(xiàn)的代碼塊復(fù)制到現(xiàn)需功能處。
while True: if cpu利用率 > 90%: #發(fā)送郵件提醒 連接郵箱服務(wù)器 發(fā)送郵件 關(guān)閉連接 if 硬盤(pán)使用空間 > 90%: #發(fā)送郵件提醒 連接郵箱服務(wù)器 發(fā)送郵件 關(guān)閉連接 if 內(nèi)存占用 > 80%: #發(fā)送郵件提醒 連接郵箱服務(wù)器 發(fā)送郵件 關(guān)閉連接
隨著時(shí)間的推移,開(kāi)始使用了函數(shù)式編程,增強(qiáng)代碼的重用性和可讀性,就變成了這樣:
def 發(fā)送郵件(內(nèi)容) #發(fā)送郵件提醒 連接郵箱服務(wù)器 發(fā)送郵件 關(guān)閉連接while True: if cpu利用率 > 90%: 發(fā)送郵件('CPU報(bào)警') if 硬盤(pán)使用空間 > 90%: 發(fā)送郵件('硬盤(pán)報(bào)警') if 內(nèi)存占用 > 80%: 發(fā)送郵件('內(nèi)存報(bào)警') 今天我們來(lái)學(xué)習(xí)一種新的編程方式:面向?qū)ο缶幊蹋∣bject Oriented PRogramming,OOP,面向?qū)ο蟪绦蛟O(shè)計(jì))
注:java和C#來(lái)說(shuō)只支持面向?qū)ο缶幊蹋鴓ython比較靈活即支持面向?qū)ο缶幊桃仓С趾瘮?shù)式編程
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用 “類(lèi)” 和 “對(duì)象” 來(lái)實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類(lèi)” 和 “對(duì)象” 的使用。
類(lèi)就是一個(gè)模板,模板里可以包含多個(gè)函數(shù),函數(shù)里實(shí)現(xiàn)一些功能
對(duì)象則是根據(jù)模板創(chuàng)建的實(shí)例,通過(guò)實(shí)例對(duì)象可以執(zhí)行類(lèi)中的函數(shù)

ps:類(lèi)中的函數(shù)第一個(gè)參數(shù)必須是self(詳細(xì)見(jiàn):類(lèi)的三大特性之封裝)
類(lèi)中定義的函數(shù)叫做 “方法”
# 創(chuàng)建類(lèi)class Foo: def Bar(self): print 'Bar' def Hello(self, name): print 'i am %s' %name# 根據(jù)類(lèi)Foo創(chuàng)建對(duì)象objobj = Foo()obj.Bar() #執(zhí)行Bar方法obj.Hello('wupeiqi') #執(zhí)行Hello方法 誒,你在這里是不是有疑問(wèn)了?使用函數(shù)式編程和面向?qū)ο缶幊谭绞絹?lái)執(zhí)行一個(gè)“方法”時(shí)函數(shù)要比面向?qū)ο蠛?jiǎn)便
觀(guān)察上述對(duì)比答案則是肯定的,然后并非絕對(duì),場(chǎng)景的不同適合其的編程方式也不同。
總結(jié):函數(shù)式的應(yīng)用場(chǎng)景 --> 各個(gè)函數(shù)之間是獨(dú)立且無(wú)共用的數(shù)據(jù)
面向?qū)ο蟮娜筇匦允侵福悍庋b、繼承和多態(tài)。
一、封裝
封裝,顧名思義就是將內(nèi)容封裝到某個(gè)地方,以后再去調(diào)用被封裝在某處的內(nèi)容。
所以,在使用面向?qū)ο蟮姆庋b特性時(shí),需要:
第一步:將內(nèi)容封裝到某處

self 是一個(gè)形式參數(shù),當(dāng)執(zhí)行 obj1 = Foo('wupeiqi', 18 ) 時(shí),self 等于 obj1
當(dāng)執(zhí)行 obj2 = Foo('alex', 78 ) 時(shí),self 等于 obj2
所以,內(nèi)容其實(shí)被封裝到了對(duì)象 obj1 和 obj2 中,每個(gè)對(duì)象中都有 name 和 age 屬性,在內(nèi)存里類(lèi)似于下圖來(lái)保存。

第二步:從某處調(diào)用被封裝的內(nèi)容
調(diào)用被封裝的內(nèi)容時(shí),有兩種情況:
1、通過(guò)對(duì)象直接調(diào)用被封裝的內(nèi)容
上圖展示了對(duì)象 obj1 和 obj2 在內(nèi)存中保存的方式,根據(jù)保存格式可以如此調(diào)用被封裝的內(nèi)容:對(duì)象.屬性名
class Foo: def __init__(self, name, age): self.name = name self.age = ageobj1 = Foo('wupeiqi', 18) print obj1.name # 直接調(diào)用obj1對(duì)象的name屬性print obj1.age # 直接調(diào)用obj1對(duì)象的age屬性obj2 = Foo('alex', 73)print obj2.name # 直接調(diào)用obj2對(duì)象的name屬性print obj2.age # 直接調(diào)用obj2對(duì)象的age屬性2、通過(guò)self間接調(diào)用被封裝的內(nèi)容
執(zhí)行類(lèi)中的方法時(shí),需要通過(guò)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)會(huì)將obj1傳給self參數(shù),即:obj1.detail(obj1),所以,此時(shí)方法內(nèi)部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo('alex', 73)obj2.detail() # Python默認(rèn)會(huì)將obj2傳給self參數(shù),即:obj1.detail(obj2),所以,此時(shí)方法內(nèi)部的 self = obj2,即:self.name 是 alex ; self.age 是 78綜上所述,對(duì)于面向?qū)ο蟮姆庋b來(lái)說(shuō),其實(shí)就是使用構(gòu)造方法將內(nèi)容封裝到 對(duì)象 中,然后通過(guò)對(duì)象直接或者self間接獲取被封裝的內(nèi)容。
練習(xí)一:在終端輸出如下信息
- 小明,10歲,男,上山去砍柴
- 小明,10歲,男,開(kāi)車(chē)去東北
- 小明,10歲,男,最?lèi)?ài)大保健
- 老李,90歲,男,上山去砍柴
- 老李,90歲,男,開(kāi)車(chē)去東北
- 老李,90歲,男,最?lèi)?ài)大保健
- 老張...
函數(shù)式編程def kanchai(name, age, gender): print "%s,%s歲,%s,上山去砍柴" %(name, age, gender)def qudongbei(name, age, gender): print "%s,%s歲,%s,開(kāi)車(chē)去東北" %(name, age, gender)def dabaojian(name, age, gender): print "%s,%s歲,%s,最?lèi)?ài)大保健" %(name, age, gender)kanchai('小明', 10, '男')qudongbei('小明', 10, '男')dabaojian('小明', 10, '男')kanchai('老李', 90, '男')qudongbei('老李', 90, '男')dabaojian('老李', 90, '男')面向?qū)ο?/span>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,開(kāi)車(chē)去東北" %(self.name, self.age, self.gender) def dabaojian(self): print "%s,%s歲,%s,最?lèi)?ài)大保健" %(self.name, self.age, self.gender)xiaoming = Foo('小明', 10, '男')xiaoming.kanchai()xiaoming.qudongbei()xiaoming.dabaojian()laoli = Foo('老李', 90, '男')laoli.kanchai()laoli.qudongbei()laoli.dabaojian()上述對(duì)比可以看出,如果使用函數(shù)式編程,需要在每次執(zhí)行函數(shù)時(shí)傳入相同的參數(shù),如果參數(shù)多的話(huà),又需要粘貼復(fù)制了... ;而對(duì)于面向?qū)ο笾恍枰趧?chuàng)建對(duì)象時(shí),將所有需要的參數(shù)封裝到當(dāng)前對(duì)象中,之后再次使用時(shí),通過(guò)self間接去當(dāng)前對(duì)象中取值即可。
練習(xí)二:游戲人生程序
1、創(chuàng)建三個(gè)游戲人物,分別是:
- 蒼井井,女,18,初始戰(zhàn)斗力1000
- 東尼木木,男,20,初始戰(zhàn)斗力1800
- 波多多,女,19,初始戰(zhàn)斗力2500
2、游戲場(chǎng)景,分別:
- 草叢戰(zhàn)斗,消耗200戰(zhàn)斗力
- 自我修煉,增長(zhǎng)100戰(zhàn)斗力
- 多人游戲,消耗500戰(zhàn)斗力
游戲人生# -*- coding:utf-8 -*-# ##################### 定義實(shí)現(xiàn)功能的類(lèi) #####################class Person: def __init__(self, na, gen, age, fig): self.name = na self.gender = gen self.age = age self.fight =fig def grassland(self): """注釋?zhuān)翰輩矐?zhàn)斗,消耗200戰(zhàn)斗力""" self.fight = self.fight - 200 def practice(self): """注釋?zhuān)鹤晕倚逕挘鲩L(zhǎng)100戰(zhàn)斗力""" self.fight = self.fight + 200 def incest(self): """注釋?zhuān)憾嗳擞螒颍?00戰(zhàn)斗力""" self.fight = self.fight - 500 def detail(self): """注釋?zhuān)寒?dāng)前對(duì)象的詳細(xì)情況""" temp = "姓名:%s ; 性別:%s ; 年齡:%s ; 戰(zhàn)斗力:%s" % (self.name, self.gender, self.age, self.fight) print temp # ##################### 開(kāi)始游戲 #####################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() #東尼木木也參加了一個(gè)多人游戲bo.practice() #波多多自我修煉了一次#輸出當(dāng)前所有人的詳細(xì)情況cang.detail()dong.detail()bo.detail()
二、繼承
繼承,面向?qū)ο笾械睦^承和現(xiàn)實(shí)生活中的繼承相同,即:子可以繼承父的內(nèi)容。
例如:
貓可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我們要分別為貓和狗創(chuàng)建一個(gè)類(lèi),那么就需要為 貓 和 狗 實(shí)現(xiàn)他們所有的功能,如下所示:

class 貓: def 喵喵叫(self): print '喵喵叫' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do somethingclass 狗: def 汪汪叫(self): print '喵喵叫' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類(lèi)中編寫(xiě)了兩次。如果使用 繼承 的思想,如下實(shí)現(xiàn):
動(dòng)物:吃、喝、拉、撒
貓:喵喵叫(貓繼承動(dòng)物的功能)
狗:汪汪叫(狗繼承動(dòng)物的功能)

class 動(dòng)物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something# 在類(lèi)后面括號(hào)中寫(xiě)入另外一個(gè)類(lèi)名,表示當(dāng)前類(lèi)繼承另外一個(gè)類(lèi)class 貓(動(dòng)物): def 喵喵叫(self): print '喵喵叫' # 在類(lèi)后面括號(hào)中寫(xiě)入另外一個(gè)類(lèi)名,表示當(dāng)前類(lèi)繼承另外一個(gè)類(lèi)class 狗(動(dòng)物): 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.nameclass 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()
所以,對(duì)于面向?qū)ο蟮睦^承來(lái)說(shuō),其實(shí)就是將多個(gè)類(lèi)共有的方法提取到父類(lèi)中,子類(lèi)僅需繼承父類(lèi)而不必一一實(shí)現(xiàn)每個(gè)方法。
注:除了子類(lèi)和父類(lèi)的稱(chēng)謂,你可能看到過(guò) 派生類(lèi) 和 基類(lèi) ,他們與子類(lèi)和父類(lèi)只是叫法不同而已。

學(xué)習(xí)了繼承的寫(xiě)法之后,我們用代碼來(lái)是上述阿貓阿狗的功能:

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.nameclass 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()
那么問(wèn)題又來(lái)了,多繼承呢?
1、Python的類(lèi)可以繼承多個(gè)類(lèi),Java和C#中則只能繼承一個(gè)類(lèi)
2、Python的類(lèi)如果繼承了多個(gè)類(lèi),那么其尋找方法的方式有兩種,分別是:深度優(yōu)先和廣度優(yōu)先

經(jīng)典類(lèi)和新式類(lèi),從字面上可以看出一個(gè)老一個(gè)新,新的必然包含了跟多的功能,也是之后推薦的寫(xiě)法,從寫(xiě)法上區(qū)分的話(huà),如果 當(dāng)前類(lèi)或者父類(lèi)繼承了object類(lèi),那么該類(lèi)便是新式類(lèi),否則便是經(jīng)典類(lèi)。


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方法時(shí)# 首先去A類(lèi)中查找,如果A類(lèi)中沒(méi)有,則繼續(xù)去B類(lèi)中找,如果B類(lèi)中么有,則繼續(xù)去D類(lèi)中找,如果D類(lèi)中么有,則繼續(xù)去C類(lèi)中找,如果還是未找到,則報(bào)錯(cuò)# 所以,查找順序:A --> B --> D --> C# 在上述查找bar方法的過(guò)程中,一旦找到,則尋找過(guò)程立即中斷,便不會(huì)再繼續(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方法時(shí)# 首先去A類(lèi)中查找,如果A類(lèi)中沒(méi)有,則繼續(xù)去B類(lèi)中找,如果B類(lèi)中么有,則繼續(xù)去C類(lèi)中找,如果C類(lèi)中么有,則繼續(xù)去D類(lèi)中找,如果還是未找到,則報(bào)錯(cuò)# 所以,查找順序:A --> B --> C --> D# 在上述查找bar方法的過(guò)程中,一旦找到,則尋找過(guò)程立即中斷,便不會(huì)再繼續(xù)找了a.bar()
經(jīng)典類(lèi):首先去A類(lèi)中查找,如果A類(lèi)中沒(méi)有,則繼續(xù)去B類(lèi)中找,如果B類(lèi)中么有,則繼續(xù)去D類(lèi)中找,如果D類(lèi)中么有,則繼續(xù)去C類(lèi)中找,如果還是未找到,則報(bào)錯(cuò)
新式類(lèi):首先去A類(lèi)中查找,如果A類(lèi)中沒(méi)有,則繼續(xù)去B類(lèi)中找,如果B類(lèi)中么有,則繼續(xù)去C類(lèi)中找,如果C類(lèi)中么有,則繼續(xù)去D類(lèi)中找,如果還是未找到,則報(bào)錯(cuò)
注意:在上述查找過(guò)程中,一旦找到,則尋找過(guò)程立即中斷,便不會(huì)再繼續(xù)找了
三、多態(tài)
Pyhon不支持多態(tài)并且也用不到多態(tài),多態(tài)的概念是應(yīng)用于Java和C#這一類(lèi)強(qiáng)類(lèi)型語(yǔ)言中,而Python崇尚“鴨子類(lèi)型”。

class F1: passclass S1(F1): def show(self): print 'S1.show'class S2(F1): def show(self): print 'S2.show'# 由于在Java或C#中定義函數(shù)參數(shù)時(shí),必須指定參數(shù)的類(lèi)型# 為了讓Func函數(shù)既可以執(zhí)行S1對(duì)象的show方法,又可以執(zhí)行S2對(duì)象的show方法,所以,定義了一個(gè)S1和S2類(lèi)的父類(lèi)# 而實(shí)際傳入的參數(shù)是:S1對(duì)象和S2對(duì)象def Func(F1 obj): """Func函數(shù)需要接收一個(gè)F1類(lèi)型或者F1子類(lèi)的類(lèi)型""" print obj.show() s1_obj = S1()Func(s1_obj) # 在Func函數(shù)中傳入S1類(lèi)的對(duì)象 s1_obj,執(zhí)行 S1 的show方法,結(jié)果:S1.shows2_obj = S2()Func(s2_obj) # 在Func函數(shù)中傳入Ss類(lèi)的對(duì)象 ss_obj,執(zhí)行 Ss 的show方法,結(jié)果:S2.show

class F1: passclass 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é)對(duì)于面向?qū)ο蟪跫?jí)知識(shí)的介紹,總結(jié)如下:
問(wèn)答專(zhuān)區(qū)
問(wèn)題一:什么樣的代碼才是面向?qū)ο螅?/strong>
答:從簡(jiǎn)單來(lái)說(shuō),如果程序中的所有功能都是用 類(lèi) 和 對(duì)象 來(lái)實(shí)現(xiàn),那么就是面向?qū)ο缶幊塘恕?/em>
問(wèn)題二:函數(shù)式編程 和 面向?qū)ο?如何選擇?分別在什么情況下使用?
答:須知:對(duì)于 C# 和 Java 程序員來(lái)說(shuō)不存在這個(gè)問(wèn)題,因?yàn)樵搩砷T(mén)語(yǔ)言只支持面向?qū)ο缶幊蹋ú恢С趾瘮?shù)式編程)。而對(duì)于 Python 和 php 等語(yǔ)言卻同時(shí)支持兩種編程方式,且函數(shù)式編程能完成的操作,面向?qū)ο蠖伎梢詫?shí)現(xiàn);而面向?qū)ο蟮哪芡瓿傻牟僮鳎瘮?shù)式編程不行(函數(shù)式編程無(wú)法實(shí)現(xiàn)面向?qū)ο蟮姆庋b功能)。
所以,一般在Python開(kāi)發(fā)中,全部使用面向?qū)ο?/span> 或 面向?qū)ο蠛秃瘮?shù)式混合使用
面向?qū)ο蟮膽?yīng)用場(chǎng)景:

class SqlHelper: def __init__(self, host, user, pwd): self.host = host self.user = user self.pwd = pwd def 增(self): # 使用主機(jī)名、用戶(hù)名、密碼(self.host 、self.user 、self.pwd)打開(kāi)數(shù)據(jù)庫(kù)連接 # do something # 關(guān)閉數(shù)據(jù)庫(kù)連接 def 刪(self): # 使用主機(jī)名、用戶(hù)名、密碼(self.host 、self.user 、self.pwd)打開(kāi)數(shù)據(jù)庫(kù)連接 # do something # 關(guān)閉數(shù)據(jù)庫(kù)連接 def 改(self): # 使用主機(jī)名、用戶(hù)名、密碼(self.host 、self.user 、self.pwd)打開(kāi)數(shù)據(jù)庫(kù)連接 # do something # 關(guān)閉數(shù)據(jù)庫(kù)連接 def 查(self): # 使用主機(jī)名、用戶(hù)名、密碼(self.host 、self.user 、self.pwd)打開(kāi)數(shù)據(jù)庫(kù)連接 # do something # 關(guān)閉數(shù)據(jù)庫(kù)連接# do something

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 tempzhangsan = Person('張三', 18, 'A')lisi = Person('李四', 73, 'AB')yangwu = Person('楊五', 84, 'A')
問(wèn)題三:類(lèi)和對(duì)象在內(nèi)存中是如何保存?
答:類(lèi)以及類(lèi)中的方法在內(nèi)存中只有一份,而根據(jù)類(lèi)創(chuàng)建的每一個(gè)對(duì)象都在內(nèi)存中需要存一份,大致如下圖:

如上圖所示,根據(jù)類(lèi)創(chuàng)建對(duì)象時(shí),對(duì)象中除了封裝 name 和 age 的值之外,還會(huì)保存一個(gè)類(lèi)對(duì)象指針,該值指向當(dāng)前對(duì)象的類(lèi)。
當(dāng)通過(guò) obj1 執(zhí)行 【方法一】 時(shí),過(guò)程如下:
以上是本篇文章的全部?jī)?nèi)容,如果覺(jué)得有點(diǎn)點(diǎn)意思,右邊的推薦再等你喲 ! 下一篇《面向?qū)ο筮M(jìn)階篇》
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注