摘要:被繼承的類稱為父類基類或超類,新的類稱為子類或派生類。但要注意的是,繼承關(guān)系應(yīng)只發(fā)生在有較強(qiáng)相互關(guān)系的類之間,比如從車類派生出電動(dòng)車類,沒有從車類派生出哈士奇這種騷操作。
《Python編程:從入門到實(shí)踐》筆記。1. 概述
本章主要介紹一種重要的編程思想:面向?qū)ο缶幊?,包括了類與對(duì)象等概念及操作。
面向?qū)ο缶幊?Object-oriented programming, OOP)是最有效的軟件編寫方法之一。面向?qū)ο蟮乃枷胍彩侨祟愖怨耪J(rèn)識(shí)世界的方法,即“分門別類”。而在以往的經(jīng)驗(yàn)里,筆者印象最深刻的面向?qū)ο笏枷刖褪侵袑W(xué)生物課本上對(duì)自然界的分類:界門綱目科屬種。這里要明白兩個(gè)概念:類與對(duì)象。類是一個(gè)總的抽象概念,是一群相似事物的總括,是一個(gè)虛的概念,而這些“事物”便是對(duì)象,例如:“狗”這一概念,這就是一個(gè)“類”,哪怕是具體到某一個(gè)特定的種類,比如哈士奇,這也是個(gè)類,只有當(dāng)真正具體到某一條狗時(shí),比如“你家的哈士奇A”,這才到達(dá)了“對(duì)象”這一概念,綜上:類是抽象的,對(duì)象是實(shí)際的。而從類到對(duì)象的過程,就叫做類的實(shí)例化。
2. 創(chuàng)建和使用類 2.1 創(chuàng)建一個(gè)Car類在Python中類名一般采用駝峰命名法,即每個(gè)單詞的首字母大寫,而不使用下劃線,實(shí)例名和模塊名都采用小寫,用下劃線拼接。并且,不論是在寫函數(shù),類,還是代碼文件,最好都加上一個(gè)文檔字符串,比如下面的三引號(hào)字符串。
class Car: """一次模擬汽車的簡(jiǎn)單嘗試""" def __init__(self, make, model, year): """初始化描述汽車的屬性""" self.make = make self.model = model self.year = year self.odometer_reading = 0 # 里程表 def get_descriptive_name(self): """返回整潔的描述性信息""" long_name = str(self.year) + " " + self.make + " " + self.model return long_name.title() def read_odometer(self): """打印一條指出汽車歷程的消息""" print("This car has " + str(self.odometer_reading) + " miles on it.") def update_odometer(self, mileage): """將里程表讀書設(shè)置為指定的值,且禁止讀數(shù)回調(diào)""" if mileage <= 0: print("Mileage must be bigger than 0!") elif mileage >= self.odometer_reading: self.odometer_reading = mileage else: print("You can"t roll back an odometer!") def increment_odometer(self, miles): """將里程表讀數(shù)增加指定的量,且該量必須為正數(shù)""" if miles > 0: self.odometer_reading += miles else: print("Mile must be bigger than 0!") def fill_gas_tank(self): """將油箱裝滿""" print("The gas tank has been filled!")
以下有幾點(diǎn)需要注意:
①類中的函數(shù)稱為方法,比如上述定義的三個(gè)函數(shù);類中與self相綁定的變量稱為屬性,比如make,model,year(不是指那三個(gè)形參,而是與self綁定的變量)。
②每一個(gè)類必有一個(gè)__init()__方法,這個(gè)方法被稱為構(gòu)造方法(在C++中被稱為構(gòu)造函數(shù),不過不用太糾結(jié)到底是“方法”還是“函數(shù)”,一個(gè)東西放在了不同地方有了不同的名字而已)。當(dāng)然它也有默認(rèn)的版本,即只有一個(gè)self參數(shù),并且該函數(shù)什么也不做,這也表明,你甚至都不用定義這個(gè)方法,到時(shí)候Python會(huì)自動(dòng)生成并調(diào)用默認(rèn)構(gòu)造方法,不過“不定義構(gòu)造方法”這種情況估計(jì)也就只有像筆者這樣初學(xué)的時(shí)候才能遇到 ^_^。
③Python中self參數(shù)是類中每個(gè)非靜態(tài)方法必須要有的形參,且必須放在第一個(gè),它是一個(gè)指向?qū)嵗旧恚ú皇穷惐旧恚。┑囊粋€(gè)引用,讓實(shí)例能夠訪問類中的屬性和方法,我們?cè)谡{(diào)用類的方法時(shí)不用手動(dòng)傳入該參數(shù),它會(huì)自動(dòng)被傳入。類中的屬性在類中所有的方法里都能被訪問,這便是通過self參數(shù)實(shí)現(xiàn)的。如果站在C++的角度理解,self就相當(dāng)于C++類里的this指針,指向?qū)ο笞陨怼?/p>
④類中的每個(gè)屬性都必須有初始值,哪怕這個(gè)值是0,空字符串或者None。比如本例中的四個(gè)屬性,前三個(gè)屬性的值由用戶傳入,odometer_reading的值被設(shè)為了0。
⑤在上述代碼的第一行類名Car后面可帶可不帶小括號(hào),即class Car:這種寫法可行,class Car():這種寫法也可以。
2.2 使用該Car類以下代碼創(chuàng)建了一個(gè)Car類的對(duì)象,并對(duì)該對(duì)象進(jìn)行了簡(jiǎn)單的操作。
# 代碼: class Car: -- snip -- # 這不是一個(gè)Python語法!這里只是表示省略。 my_new_car = Car("audi", "a4", 2016) print(my_new_car.get_descriptive_name()) my_new_car.read_odometer() # 直接修改屬性 my_new_car.odometer_reading = -100 my_new_car.read_odometer() my_new_car.odometer_reading += -1 my_new_car.read_odometer() # 通過方法修改屬性 my_new_car.update_odometer(-100) my_new_car.read_odometer() my_new_car.increment_odometer(-1) my_new_car.read_odometer() my_new_car.update_odometer(100) my_new_car.read_odometer() my_new_car.increment_odometer(1) my_new_car.read_odometer() # 結(jié)果: 2016 Audi A4 This car has 0 miles on it. This car has -100 miles on it. This car has -101 miles on it. Mileage must be bigger than 0! This car has -101 miles on it. Mile must be bigger than 0! This car has -101 miles on it. This car has 100 miles on it. This car has 101 miles on it.
從上述代碼可以看出,Python和C++,Java一樣,也是使用句點(diǎn)表示法來訪問屬性以及調(diào)用方法。從上述代碼及結(jié)果可以看出,實(shí)例的屬性可以直接也可以通過方法進(jìn)行訪問和修改。
直接訪問對(duì)象的屬性可以使操作變得簡(jiǎn)單,但這違反了封閉性原則,并且直接修改屬性也不利于規(guī)范對(duì)屬性的操作。比如代碼中將里程設(shè)置為一個(gè)負(fù)值,且在增加里程時(shí)增量也是一個(gè)負(fù)值,這顯然不符合常理(雖然有時(shí)也可以這么做)。而如果將對(duì)屬性的操作放入方法中,則可以規(guī)范這些操作,如上述的read_odometer(),update_odometer(),increment_odometer()等方法。并且這也是面向?qū)ο缶幊趟岢淖龇?,盡量不要將屬性直接對(duì)外暴露。但可惜的是,Python中任何種類的屬性都能被直接操作。
3. 繼承編寫類時(shí)并非總是從零開始,如果要編寫的類是現(xiàn)有類的特殊版本,即有相同或相似的屬性和方法,則可以從現(xiàn)有類繼承(派生)出新的類。被繼承的類稱為“父類”、“基類”或“超類(superclass)”,新的類稱為“子類“或”派生類“。
但要注意的是,繼承關(guān)系應(yīng)只發(fā)生在有較強(qiáng)相互關(guān)系的類之間,比如從車類派生出電動(dòng)車類,沒有從車類派生出哈士奇這種騷操作。
以下是從Car類派生出ElectricCar類的代碼:
# 代碼: class Car: -- snip -- class ElectricCar(Car): """電動(dòng)汽車的獨(dú)特之處""" def __init__(self, make, model, year): """初始化父類的屬性,再初始化電動(dòng)汽車特有的屬性""" super().__init__(make, model, year) self.battery_size = 70 def describe_battery(self): """打印一條描述電池容量的消息""" print("This car has a " + str(self.battery_size) + "-kWh battery.") def fill_gas_tank(self): # 重寫了父類的方法 """電動(dòng)車沒有油箱""" print("This car doesn"t need a gas tank!") my_audi = Car("audi", "a4", 2018) print(my_audi.get_descriptive_name()) my_audi.fill_gas_tank() print() # 用作空行 my_tesla = ElectricCar("tesla", "model s", 2018) print(my_tesla.get_descriptive_name()) my_tesla.describe_battery() my_tesla.fill_gas_tank() # 結(jié)果: 2018 Audi A4 The gas tank has been filled! 2018 Tesla Model S This car has a 70-kWh battery. This car doesn"t need a gas tank!
從以上代碼可以總結(jié)出幾點(diǎn):
①創(chuàng)建子類的實(shí)例時(shí),Python首先需要對(duì)父類進(jìn)行初始化操作,通過super()函數(shù)返回父類的引用,然后再調(diào)用父類的構(gòu)造方法,即super().__init__(參數(shù)列表)。在Python2中,對(duì)父類的初始化需要以如下方式初始化父類:
super(ElectricCar, self).__init__(make, model, year)
在Python3中也可以按上述方式來初始化父類,但也可以在單繼承時(shí)省略super()函數(shù)中的參數(shù)。
②子類可以訪問父類的所有屬性,還可以增加新的屬性:my_tesla對(duì)象訪問了父類的make, model, year等屬性,并且還增加了battery_size屬性。
③子類可以重寫父類的方法:ElectricCar類重寫了Car類的fill_gas_tank()方法。
這里需要區(qū)分兩個(gè)概念:重寫(Override)與重載(Overload)
重寫也叫覆蓋,主要是用在繼承上。當(dāng)繼承關(guān)系上的類中有相同的方法,但子類和父類在該方法中的操作不相同時(shí),子類對(duì)該方法進(jìn)行重新編寫,覆蓋掉從父類繼承下來的方法。在調(diào)用時(shí),Python會(huì)自動(dòng)判斷該對(duì)象是否是派生類來調(diào)用該方法相應(yīng)的實(shí)現(xiàn)。正是有了重寫,面向?qū)ο笾?strong>多態(tài)(Polymorphism)這一特性才得以實(shí)現(xiàn)。
重載主要用于函數(shù)(方法)。在像C/C++,Java這樣的語言中,可以有多個(gè)同名的函數(shù),但參數(shù)列表必須不相同,比如參數(shù)個(gè)數(shù),參數(shù)類型不相同。這些語言則根據(jù)參數(shù)列表來區(qū)分到底調(diào)用的是同名函數(shù)中的哪一個(gè)函數(shù)。但重載并不屬于多態(tài)性!這些語言在編譯源文件的時(shí)候,會(huì)根據(jù)參數(shù)列表來對(duì)同名函數(shù)生成不同的函數(shù)名(具體方法就是添加前綴或后綴),然后將源代碼中的這些同名函數(shù)都替換成新函數(shù)名,所以重載并不屬于多態(tài)。但是Python中并沒有函數(shù)重載這種說法!因?yàn)镻ython有關(guān)鍵字參數(shù)和可變參數(shù)這種神器(當(dāng)然C++也有變長(zhǎng)參數(shù),它用三個(gè)點(diǎn)表示,不知道Python可變參數(shù)的底層實(shí)現(xiàn)是不是就和C++的變長(zhǎng)參數(shù)有關(guān))。
然而這都不重要!明白重寫和重載的概念,會(huì)用就行了,至于這倆和多態(tài)究竟有沒有關(guān)系并不重要,至今網(wǎng)上對(duì)這倆與多態(tài)的關(guān)系都沒有一個(gè)準(zhǔn)確的說法。筆者以前看C++的書的時(shí)候記得專門把重載的底層實(shí)現(xiàn)給提了出來(哪本書忘了),但筆者才疏學(xué)淺,暫不清楚重寫在編譯時(shí)是個(gè)什么情況,說不定也是靠生成新函數(shù)名并替換,如果這樣的話,那重載也可以算多態(tài)了,不過這只是筆者的猜測(cè)!感興趣的小伙伴可自行研究這倆在編譯時(shí)的情況。
之所以把這倆多帶帶提出來,主要是好多人在考研復(fù)試或者找工作面試的時(shí)候載到了這個(gè)概念上。尤其是考研,考研復(fù)試似乎更傾向于重寫屬于多態(tài),重載不屬于多態(tài)。
3.1 將實(shí)例用作屬性使用代碼模擬實(shí)物時(shí),隨著開發(fā)的進(jìn)展,勢(shì)必一個(gè)類的屬性和方法將會(huì)越來越多,單單一個(gè)類的代碼就會(huì)越來越長(zhǎng)。這時(shí)可以考慮是否能將其中一部分代碼多帶帶提取出來作為一個(gè)新的類。比如前面的ElectricCar類里的電池就可以多帶帶提出來作為一個(gè)類。
# 代碼: class Car: -- snip -- class Battery: """一次模擬電動(dòng)汽車電池的簡(jiǎn)單嘗試""" def __init__(self, battery_size=70): """初始化電池的屬性""" self.battery_size = battery_size def describe_battery(self): """打印一條描述電池容量的信息""" print("This car has a " + str(self.battery_size) + "-kWh battery.") def get_range(self): """輸出電池的續(xù)航里程""" if self.battery_size == 70: miles = 240 elif self.battery_size == 85: miles = 270 message = "This car can go approximately " + str(miles) + " miles on a full charge." print(message) class ElectricCar(Car): def __init__(self, make, model, year): super().__init__(make, model, year) self.battery = Battery() my_tesla = ElectricCar("tesla", "model s", 2018) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range() # 結(jié)果: 2018 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge.
模擬復(fù)雜的實(shí)物時(shí),需要解決一些有趣的問題,比如續(xù)航里程是電池的屬性還是汽車的屬性呢?如果只描述一輛車,那將get_range()方法放入Battery()中并無不妥,但如果要描述整個(gè)汽車產(chǎn)品線呢?比如這一款車型能跑多遠(yuǎn),那也許將該方法放入ElectricCar類則比較合適。但不管怎樣,這里強(qiáng)調(diào)的是應(yīng)該站在一個(gè)更高的邏輯層面考慮問題。
4. 從模塊導(dǎo)入類與上一篇寫關(guān)于函數(shù)的文章相似,類也可以多帶帶形成模塊??梢砸粋€(gè)類就是一個(gè)模塊,也可以多個(gè)類(一般是相關(guān)聯(lián)的類)放入一個(gè)模塊。比如將上述的Car類多帶帶放在一個(gè)文件中,除去此類的代碼,其他代碼均刪除,最后將該文件命名為car.py(注意這里的文件名是小寫的)。然后再在程序中帶入該類:
from car import Car # 如果命名有沖突,也可以給Car類起個(gè)別名 # from car import Car as C my_new_car = Car("audi", "a4", 2018) print(my_new_car.get_descriptive_name()) my_new_car.odometer_reading = 23 my_new_car.read_odometer()
也可以將多個(gè)相關(guān)聯(lián)的類放入同一個(gè)文件中,形成一個(gè)模塊,比如上面的Car類,ElectricCar類和Battery類,將該文件命名為cars.py,最后導(dǎo)入該文件:
from cars import Car, ElectricCar my_beetle = Car("volkswagen", "beetle", 2018) my_tesla = ElectricCar("tesla", "model s", 2018) -- snip -- # 后面的代碼和之前的類似,不在贅述
也可以將整個(gè)模塊導(dǎo)入,并使用句點(diǎn)表示法使用模塊中的類:
import cars my_car = car.Car("volkswagen", "beetle", 2018) my_tesla = car.ElectricCar("tesla", "model s", 2018)
還可以導(dǎo)入模塊中的所有類(不推薦此法,容易產(chǎn)生命名沖突?。?,此時(shí)便不需要使用句點(diǎn)表示法。
from cars import * my_beetle = Car("volkswagen", "beetle", 2018)
還可以在模塊中導(dǎo)入另一個(gè)模塊,比如,將Car類多帶帶放在一個(gè)文件中形參一個(gè)模塊,命名為car.py,再新建一個(gè)模塊electric_car.py用于存放Battery類和ElectricCar類,并在該模塊中帶入Car類:
from car import Car class Battery: -- snip -- class ElectricCar(Car): -- snip --
最后在執(zhí)行文件的源代碼中根據(jù)需要導(dǎo)入類:
# 這是書中導(dǎo)入兩個(gè)類的代碼 from car import Car from electric_car import ElectricCar my_car = Car("audi", "a4", 2018) my_tesla = ElectricCar("tesla", "model s", 2018)
之前讀到這的時(shí)候覺得能不能像以下這樣的方式導(dǎo)入Car類:
from electric_car import Car, ElectricCar my_car = Car("audi", "a4", 2018) my_tesla = ElectricCar("tesla", "model s", 2018)
后來親測(cè),這樣做也是可以的。那問題就來了,像書中那樣的導(dǎo)入方式是不是發(fā)生了代碼的覆蓋呢?哪種導(dǎo)入的效率更高呢?筆者在這里還有點(diǎn)懵,后續(xù)再更新吧。
模塊導(dǎo)入的方法還有很多,甚至能直接從GitHub導(dǎo)入模塊,上述的導(dǎo)入方式只是皮毛。最后用一個(gè)從標(biāo)準(zhǔn)庫導(dǎo)入OrderedDict類的示例結(jié)束本文。之前版本的Python中普通字典類是不確保鍵值對(duì)之前的順序的,想要確保順序就得使用OrderedDict類。但現(xiàn)在從3.6版本起,Python也確保了普通字典里鍵值對(duì)也是有序的了,但是為了兼容性考慮(有可能你的代碼還要運(yùn)行在3.6之前的版本),目前還是建議使用OrderedDict類。
# 代碼: from collections import OrderedDict favorite_languages = OrderedDict() favorite_languages["jen"] = "python" favorite_languages["sarah"] = "c" favorite_languages["edward"] = "ruby" favorite_languages["phil"] = "python" for name, language in favorite_languages.items(): print(name.title() + ""s favorite_language is " + language.title()) # 結(jié)果: Jen"s favorite_language is Python Sarah"s favorite_language is C Edward"s favorite_language is Ruby Phil"s favorite_language is Python
迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41794.html
摘要:第行把具名元組以的形式返回。對(duì)序列使用和通常號(hào)兩側(cè)的序列由相同類型的數(shù)據(jù)所構(gòu)成當(dāng)然不同類型的也可以相加,返回一個(gè)新序列。從上面的結(jié)果可以看出,它雖拋出了異常,但仍完成了操作查看字節(jié)碼并不難,而且它對(duì)我們了解代碼背后的運(yùn)行機(jī)制很有幫助。 《流暢的Python》筆記。接下來的三篇都是關(guān)于Python的數(shù)據(jù)結(jié)構(gòu),本篇主要是Python中的各序列類型 1. 內(nèi)置序列類型概覽 Python標(biāo)準(zhǔn)庫...
摘要:在類的成員函數(shù)中訪問實(shí)例屬性需要以為前綴。但提供一種對(duì)私有成員的訪問方式對(duì)象名類名私有成員類中保護(hù)對(duì)象類中系統(tǒng)定義的特殊成員類中私有成員多態(tài)列表項(xiàng)目 Python學(xué)習(xí)第一天 類與對(duì)象 python的成員函數(shù)在,默認(rèn)有一個(gè)self參數(shù),這是類的成員函數(shù)與普通函數(shù)的主要區(qū)別,self,位于參數(shù)列表的開頭,self也代表類的實(shí)例(對(duì)象)自身,可以使用self引用類中的屬性和成員函數(shù)。在...
摘要:本章主要是對(duì)上一章類的補(bǔ)充。對(duì)于多態(tài)的補(bǔ)充子類可以被看成是父類的類型,但父類不能被看成是子類的類型。仍然以類為例,動(dòng)物里有哺乳動(dòng)物,卵生動(dòng)物,有能飛的動(dòng)物和不能飛的動(dòng)物,這是兩種大的分類方式。一般在中,以為結(jié)尾類的都作為接口。 《Python編程:從入門到實(shí)踐》筆記。本章主要是對(duì)上一章Python類的補(bǔ)充。 1. 從一個(gè)類派生出所有類 上一篇文章說道Python類的定義與繼承一般是如下...
摘要:本篇繼續(xù)學(xué)習(xí)之路,實(shí)現(xiàn)更多的特殊方法以讓自定義類的行為跟真正的對(duì)象一樣。之所以要讓向量不可變,是因?yàn)槲覀冊(cè)谟?jì)算向量的哈希值時(shí)需要用到和的哈希值,如果這兩個(gè)值可變,那向量的哈希值就能隨時(shí)變化,這將不是一個(gè)可散列的對(duì)象。 《流暢的Python》筆記。本篇是面向?qū)ο髴T用方法的第二篇。前一篇講的是內(nèi)置對(duì)象的結(jié)構(gòu)和行為,本篇?jiǎng)t是自定義對(duì)象。本篇繼續(xù)Python學(xué)習(xí)之路20,實(shí)現(xiàn)更多的特殊方法以讓...
摘要:使用抽象基類顯示表示接口如果類的作用是定義接口,應(yīng)該將其明確定義為抽象基類。此外,抽象基類可以作為其他類的唯一基類,混入類則決不能作為唯一的基類,除非這個(gè)混入類繼承了另一個(gè)更具體的混入這種做法非常少見。 《流暢的Python》筆記本篇是面向?qū)ο髴T用方法的第五篇,我們將繼續(xù)討論繼承,重點(diǎn)說明兩個(gè)方面:繼承內(nèi)置類型時(shí)的問題以及多重繼承。概念比較多,較為枯燥。 1. 繼承內(nèi)置類型 內(nèi)置類型...
閱讀 1949·2021-11-24 09:39
閱讀 3327·2021-09-22 14:58
閱讀 1181·2019-08-30 15:54
閱讀 3332·2019-08-29 11:33
閱讀 1801·2019-08-26 13:54
閱讀 1612·2019-08-26 13:35
閱讀 2482·2019-08-23 18:14
閱讀 778·2019-08-23 17:04