摘要:而后面函數(shù)返回的是對(duì)象中的。中的方法用于初始化類的實(shí)例對(duì)象。因?yàn)槟趁踪悹柼?hào)精靈有數(shù)千只使用繼承的方法可以大大減少代碼量且當(dāng)需要
老玩家回歸:
掛一張目前的陣容
哎, 菜是原罪啊。。。。。。
下面我們正式創(chuàng)建自己的類, 這里我們使用Python自定義某米賽爾號(hào)的精靈, 代碼如下:
class Elf: def setName(self, name): self.name = name def getName(self): return self.name def getInfo(self): return self
類的定義就像函數(shù)定義, 用 class 語句替代了 def 語句, 同樣需要執(zhí)行 class 的整段代碼這個(gè)類才會(huì)生效。進(jìn)入類定義部分后, 會(huì)創(chuàng)建出一個(gè)新的局部作用域, 后面定義的類的數(shù)據(jù)屬性和方法都是屬于此作用域的局部變量。上面創(chuàng)建的類很簡(jiǎn)單, 只有一些簡(jiǎn)單的方法。當(dāng)捕捉精靈后, 首先要為其起名字, 所以我們先編寫函數(shù) setName() 和 getName()。似乎函數(shù)中 self 參數(shù)有點(diǎn)奇怪, 我們嘗試建立具體的對(duì)象來探究該參數(shù)的作用。
>>> x = Elf() >>> y = Elf() >>> x.setName("小火猴") >>> y.setName("皮皮") >>> x.getName() 小火猴 >>> y.getName() 皮皮 >>> x.getInfo() <__main__.Elf instance at 0xXXXXXXXX> >>> y.getInfo() <__main__.Elf instance at 0xXXXXXXXX>
創(chuàng)建對(duì)象和調(diào)用一個(gè)函數(shù)很相似, 使用類名作為關(guān)鍵字創(chuàng)建一個(gè)類的對(duì)象, 實(shí)際上, Elf() 的括號(hào)里是可以有參數(shù)的, 后面我們會(huì)討論到。我們有兩只精靈, 一只是小火猴, 一只是皮皮, 并且對(duì)他們執(zhí)行 getName() , 名字正確返回。觀察 getInfo() 的輸出, 返回的是包含地址的具體對(duì)象信息, 可以看到兩個(gè)對(duì)象的地址, 是不一樣的。Python 中的self作用和 C++ 中的 *this 指針類似, 在調(diào)用 精靈對(duì)象 的 setName() 和 getName() 函數(shù)時(shí), 函數(shù)都會(huì)自動(dòng)把該對(duì)象的地址作為第一個(gè)參數(shù)傳入(該信息包含在參數(shù) self 中), 這就是為什么我們調(diào)用函數(shù)時(shí)不需要寫 self , 而在函數(shù)定義時(shí)需要把參數(shù)作為第一個(gè)參數(shù)。傳入對(duì)象地址是相當(dāng)必要的, 如果不傳入地址, 程序就不知道要訪問類的哪一個(gè)對(duì)象。
類的每個(gè)對(duì)象都會(huì)有各自的數(shù)據(jù)屬性, Elf 類中有數(shù)據(jù)屬性 name, 這是通過setName() 函數(shù)中的語句 self.name = name創(chuàng)建的。這個(gè)語句中的兩個(gè) name 是不一樣的, 它們的作用域不一樣。第一個(gè) name 通過 self 語句聲明的作用域是類 Elf() 的作用域, 將其作為對(duì)象 x 的數(shù)據(jù)屬性進(jìn)行存儲(chǔ), 而后面的 name 的作用域是函數(shù)的局部作用域, 與參數(shù)中的 name 相同。而后面 getName() 函數(shù)返回的是對(duì)象中的 name。
__init__()方法從更深層邏輯去說, 我們捕捉到精靈的那一刻應(yīng)該就有名字, 而并非捕捉后去設(shè)置。所以這里我們需要的是一個(gè)初始化手段。Python中的__init__() 方法用于初始化類的實(shí)例對(duì)象。__init__() 函數(shù)的作用一定程度上與C++的構(gòu)造函數(shù)相似, 但并不等于。C++ 的構(gòu)造函數(shù)是使用該函數(shù)去創(chuàng)建一個(gè)類的實(shí)例對(duì)象, 而Python執(zhí)行__init__() 方法時(shí)實(shí)例對(duì)象已被構(gòu)造出來。__init__()方法會(huì)在對(duì)象構(gòu)造出來后自動(dòng)執(zhí)行, 所以可以用于初始化我們所需要的數(shù)據(jù)屬性。修改Elf 類的代碼, 代碼如下:
class Elf: def __init__(self, name, gender, level): self.type = ("fire", None) self.gender = gender self.name = name self.level = level self.status = [10+2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level] # 精靈體力, 攻擊, 防御, 特攻, 特防, 速度 def getName(self): return self.name def getGender(self): return self.gender def getType(self): return self.type def getStatus(self): return self.status
在此處我們?cè)黾恿藥讉€(gè)數(shù)據(jù)的屬性: 性別、等級(jí)、能力和精靈屬性。連同前面的名字, 都放在__init__()方法進(jìn)行初始化。數(shù)據(jù)屬性是可以使用任意數(shù)據(jù)類型的, 小火猴屬性是火, 而精靈可能會(huì)有倆個(gè)屬性, 假設(shè)小火猴經(jīng)過兩次進(jìn)化稱為烈焰猩猩屬性為地面, 火系。為了保持?jǐn)?shù)據(jù)類型的一致性, 所以我們使用元組存儲(chǔ), 并讓小火猴的第二個(gè)屬性為 None。由于小火猴的屬性是固定的, 所以在__init__() 的輸入?yún)?shù)不需要 type。而精靈的能力會(huì)隨著等級(jí)的不同而不同, 所以在初始化中也需要實(shí)現(xiàn)這一點(diǎn)。我們創(chuàng)建實(shí)例對(duì)象測(cè)試代碼:
>>> x = Elf("小火猴", "male", 5) >>> y = Elf("皮皮", "female", 6) # 這里有錯(cuò)誤, 皮皮不是火系, 可以接著往下看, 思考如何改正 >>> print( x.getName(), x.getGender(), x.getStatus() ) 小火猴 male [20, 10, 10, 10, 10, 10] >>> print( y.getName(), y.getGender(), y.getStatus() ) 皮皮 female [22, 11, 11, 11, 11, 11]
這時(shí)候創(chuàng)建對(duì)象就需要參數(shù)了, 實(shí)際上這是__init__() 函數(shù)的參數(shù)。__init__() 自動(dòng)將數(shù)據(jù)屬性進(jìn)行了初始化, 然后調(diào)用相關(guān)函數(shù)能夠返回我們需要的對(duì)象的數(shù)據(jù)屬性。
對(duì)象的方法1.方法引用
類的方法和對(duì)象的方法是一樣的, 我們?cè)诙x類的方時(shí)程序沒有為類的方法分配內(nèi)存, 而在創(chuàng)建具體實(shí)例對(duì)象的程序才會(huì)為對(duì)象的每個(gè)數(shù)據(jù)屬性和方法分配內(nèi)存, 我們已經(jīng)知道定義類的方法是 def 定義的, 具體定義格式與普通函數(shù)相似, 只不過類的方法的第一個(gè)參數(shù)要為 self 參數(shù)。我們可以用普通函數(shù)實(shí)現(xiàn)對(duì)對(duì)象函數(shù)的引用:
>>> x = Elf("小火猴", "male", 5) >>> getStatus1 = x.getStatus >>> getStatus1() [20, 10, 10, 10, 10, 10]
雖然看上去似乎是調(diào)用了一個(gè)普通函數(shù), 但是 getStatus1() 這個(gè)函數(shù)是引用 x.getStatus() 的, 意味著程序還是隱性地加入了 self 參數(shù)。
2.私有化
先敲代碼:
>>> x.type ("fire", None) >>> x.getType() ("fire", None)
雖然這樣似乎很方便, 但違反了類的封裝原則。對(duì)象的狀態(tài)對(duì)于類外部應(yīng)該是不可以訪問的。為何要這樣做, 我們查看Python 的模塊源碼時(shí)會(huì)發(fā)現(xiàn)源碼里定義了很多類, 模塊中算法通過使用類是很常見的, 如果我們使用算法時(shí)能隨意訪問對(duì)象中的數(shù)據(jù)屬性, 那么很有可能在不經(jīng)意間修改算法中已經(jīng)調(diào)好的參數(shù), 這是十分尷尬的。盡管我們不會(huì)可以那么去做, 但這種無意的改動(dòng)是常有的事。一般封裝好的類都會(huì)有足夠的函數(shù)接口供程序員用, 程序員沒有必要訪問對(duì)象的具體數(shù)據(jù)類型。
為防止程序員無意間修改了對(duì)象的狀態(tài), 我們需要對(duì)類的數(shù)據(jù)屬性和方法進(jìn)行私有化。Python 不支持直接私有方式, 但可以使用一些小技巧達(dá)到私有特性的目的。為了讓方法的數(shù)據(jù)屬性或方法變?yōu)樗接? 只需要在它的名字前面加上雙下劃線即可, 修改Elf 類代碼:
# 自定義類 class Elf: def __init__(self, name, gender, level): self.__type = ("fire", None) self.__gender = gender self.__name = name self.__level = level self.__status = [10+2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level] # 精靈體力, 攻擊, 防御, 特攻, 特防, 速度 def getName(self): return self.__name def getGender(self): return self.__gender def getType(self): return self.__type def getStatus(self): return self.__status def level_up(self): self.__status = [s+1 for s in self.__status] self.__status[0] += 1 # HP每級(jí)增加2點(diǎn), 其余增加1點(diǎn) def __test(self): pass
>>> x = Elf("小火猴", "male", 5) >>> print(x.type) Traceback (most recent call last): File "seer.py", line 25, inprint(x.type) AttributeError: "Elf" object has no attribute "type"
>>> print(x.getName()) 小火猴 >>> x.test() Traceback (most recent call last): File "ser.py", line 28, inx.test() AttributeError: "Elf" object has no attribute "test"
現(xiàn)在在程序外部直接訪問私有數(shù)據(jù)是不允許的, 我們只能通過設(shè)定好的節(jié)后函數(shù)去調(diào)取對(duì)象信息。不過通過雙下劃綫實(shí)現(xiàn)的私有實(shí)際上是"偽私有化", 實(shí)際上我們還是可以做到從外部訪問這些私有屬性。
>>> print(x._Elf__type) ("fire", None)
Python 使用的是一種 name_mangling 技術(shù), 將__membername 替換成 _class__membername, 在外部使用原來的私有成員時(shí), 會(huì)提示無法找到, 而上面執(zhí)行的 x.Elf__type 是可以訪問。簡(jiǎn)而言之, 確保其他人無法訪問對(duì)象的方法和數(shù)據(jù)屬性是不可能的, 但是使用這種 name_mangling 技術(shù)是一種程序員不應(yīng)該從外部訪問這些私有成員的強(qiáng)有力信號(hào)。
可以看到代碼中還增加了一個(gè)函數(shù)level_up(), 這個(gè)函數(shù)用于處理精靈升級(jí)是能力的提升, 我們不應(yīng)該在外部修改 x 對(duì)象的 status , 所以應(yīng)準(zhǔn)備好接口去處理能力發(fā)生變化的情景, 函數(shù) level_up() 僅僅是一個(gè)簡(jiǎn)單的例子, 而據(jù)說在工業(yè)代碼中, 這樣的函數(shù)接口是大量的, 程序需要對(duì)它們進(jìn)行歸類并附上相應(yīng)的文檔說明。
3.迭代器
Python容器對(duì)象(列表、元組、字典和字符串等)都可以可以用 for 遍歷,
for element in [1, 2, 3]: print(element)
這種風(fēng)格十分簡(jiǎn)潔, for 語句在容器對(duì)象上調(diào)用了 iter(), 該函數(shù)返回一個(gè)定義了 next() 方法的迭代器對(duì)象, 它在容器中逐一訪問元素。當(dāng)容器遍歷完畢, __next__() 找不到后續(xù)元素時(shí), next() 找不到后續(xù)元素時(shí), next()會(huì)引發(fā)一個(gè) StopIteration 異常, 告知for循環(huán)終止。
>>> L = [1, 2, 3] >>> it = iter(L) >>> it>>> it.__next__() 1 >>> it.__next__() 2 >>> it.__next__() 2 >>> it.__next__() 3 >>> it.__next__() Traceback (most recent call last): File " ", line 1, in StopIteration
當(dāng)知道迭代器協(xié)議背后的機(jī)制后, 我們便可以吧迭代器加入到自己的類中。我們需要定義一個(gè)__iter__()方法, 它返回一個(gè)有 next() 方法的對(duì)象, 如果類定義了next(), __iter__()可以只返回self, 再次修改類 Elf 的代碼, 通過迭代器能輸出對(duì)象的全部信息。
class Elf: def __init__(self, name, gender, level): self.__type = ("fire", None) self.__gender = gender self.__name = name self.__level = level self.__status = [10+2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level] self.__info = [self.__name, self.__type, self.__gender, self.__level, self.__status] self.__index = -1 # 精靈體力, 攻擊, 防御, 特攻, 特防, 速度 def getName(self): return self.__name def getGender(self): return self.__gender def getType(self): return self.__type def getStatus(self): return self.__status def level_up(self): self.__status = [s+1 for s in self.__status] self.__status[0] += 1 def __iter__(self): print("名字 屬性 性別 等級(jí) 能力") return self def next(self): if self.__index == len(self.__info) - 1: raise StopIteration self.__index += 1 return self.__info[self.__index]繼承
面向?qū)ο缶幊痰暮锰幹痪褪谴a的復(fù)用, 實(shí)現(xiàn)這種重用的方法之一就是通過繼承機(jī)制。繼承是兩個(gè)類或多個(gè)類之間的父子關(guān)系, 子類繼承了基類的所有公有數(shù)據(jù)屬性和方法, 并且可以通過編寫子類的代碼擴(kuò)充子類的功能。可以說, 如果人類可以做到兒女繼承了父母的所有才學(xué)并加以拓展, 那么人類的發(fā)展至少是現(xiàn)在的數(shù)萬倍。繼承實(shí)現(xiàn)了數(shù)據(jù)屬性和方法的重用, 減少了代碼的冗余度。
那么我們?nèi)绾螌?shí)現(xiàn)繼承呢??如果我們需要的類中具有公共的成員, 且具有一定的遞進(jìn)關(guān)系, 那么就可以使用繼承, 且讓結(jié)構(gòu)最簡(jiǎn)單的類作為基類。一般來說, 子類是父類的特殊化, 如下關(guān)系:
哺乳類動(dòng)物 ————> 貓科動(dòng)物 ————> 東北虎
東北虎類 繼承 貓科動(dòng)物類, 貓科動(dòng)物類 繼承 哺乳動(dòng)物類, 貓科動(dòng)物類編寫了所有貓科動(dòng)物公有的行為的方法而特定貓類則增加了該貓科動(dòng)物特有的行為。不過繼承也有一定弊端, 可能基類對(duì)于子類也有一定特殊的地方, 如果某種特定貓科動(dòng)物不具有絕大多數(shù)貓科動(dòng)物的行為, 當(dāng)程序員嗎,沒有理清類之間的關(guān)系是, 可能使子類具有不該有的方法。另外, 如果繼承鏈太長(zhǎng)的話, 任何一點(diǎn)小的變化都會(huì)引起一連串變化, 我們使用的繼承要注意控制繼承鏈的規(guī)模。
繼承語法: class 子類名(基類名1, 基類名2,...), 基類卸載括號(hào)里, 如果有多個(gè)基類, 則全部寫在括號(hào)里, 這種情況稱為多繼承。在Python中繼承有以下一些特點(diǎn):
1) 在繼承中積累初始化方法__init__()函數(shù)不會(huì)被自動(dòng)調(diào)用。如果希望子類調(diào)用基類的__init__() 方法, 需要在子類的 __init__() 方法中顯示調(diào)用它。這與C++差別很大。
2) 在調(diào)用基類的方法時(shí), 需要加上基類的類名前綴, 且?guī)?self 參數(shù)變量, 注意在類中調(diào)用該類中定義的方法時(shí)不需要self參數(shù)。
3) Python總是首先查找對(duì)應(yīng)類的方法, 如果在子類中沒有對(duì)應(yīng)的方法, Python才會(huì)在繼承鏈的基類中按順序查找。
4) 在Python繼承中, 子類不能訪問基類的私有成員。
這是最后一次修改類Elf的代碼:
class pokemon: def __init__(self, name, gender, level, type, status): self.__type = type self.__gender = gender self.__name = name self.__level = level self.__status = status self.__info = [self.__name, self.__type, self.__gender, self.__level, self.__status] self.__index = -1 # 精靈體力, 攻擊, 防御, 特攻, 特防, 速度 def getName(self): return self.__name def getGender(self): return self.__gender def getType(self): return self.__type def getStatus(self): return self.__status def level_up(self): self.__status = [s+1 for s in self.__status] self.__status[0] += 1 def __iter__(self): print("名字 屬性 性別 等級(jí) 能力") return self def next(self): if self.__index == len(self.__info) - 1: raise StopIteration self.__index += 1 return self.__info[self.__index] class Elf(pokemon): def __init__(self, name, gender, level): self.__type = ("fire", None) self.__gender = gender self.__name = name self.__level = level self.__status = [10+2*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level, 5+1*level] pokemon.__init__(self, self.__name, self.__gender, self.__level, self.__type, self.__status)
>>> x = Elf("小火猴", "male", 5) >>> print(x.getGender()) male >>> for info in x: print(info) 小火猴 ("fire", None) male 5 [20, 10, 10, 10, 10, 10]
我們定義了Elf 類的基類pokemon, 將精靈共有的行為都放到基類中, 子類僅僅需要向基類傳輸數(shù)據(jù)屬性即可。這樣就可以很輕松地定義其他基于pokemon類的子類。因?yàn)槟趁踪悹柼?hào)精靈有數(shù)千只, 使用繼承的方法可以大大減少代碼量, 且當(dāng)需要對(duì)全部精靈進(jìn)行整體改變時(shí)僅需改變pokemanl類的__init__()即可, 并向基類傳輸數(shù)據(jù), 這里注意要加self參數(shù), Elf 類沒有繼承基類的私有數(shù)據(jù)屬性, 因此在子類只有一個(gè)self.__type, 不會(huì)出現(xiàn)因繼承所造成的重名情況。為了能更加清晰地描述這個(gè)問題, 這里再舉一個(gè)例子:
class animal: def __init__(self): self.__age = age def print2(self): pritn(self.__age) class dog(animal): def__init__(self, age): animal.__init__(self, age) def print2(self): print(self.__age)
>>> a_animal = animal(10) >>> a_animal.print2() 10 >>> a_dog = dog(10) >>> a_dog.print2() Traceback (most recent call last): File "seer.py", line 13, inThat"s all ! (時(shí)間有限, 我會(huì)不斷完善修改的)a_dog.print2() File "seer.py", line 11, in print2 print(self.__age) AttributeError: "dog" object has no attribute "_dog__age"
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/43050.html
環(huán)境:python 3.6.8 以某米賽爾號(hào)舉個(gè)例子吧: showImg(https://segmentfault.com/img/bVboqzz?w=396&h=215);showImg(https://segmentfault.com/img/bVboqzA?w=362&h=216); >>> pd.read_excel(1.xlsx, sheet_name=Sheet2) 名字 ...
摘要:并不是因?yàn)樗情W亮的新事物或者它是一些虛構(gòu)的最佳實(shí)踐,而是因?yàn)橄駚嗰R遜或者已經(jīng)在這上面投入了年的心血,他們告訴了我們?nèi)绾螛?gòu)建真正有規(guī)模的系統(tǒng)。截止目前,我們已經(jīng)部署了由亞馬遜等提供的重量級(jí)虛擬化服務(wù)器。 周一時(shí)候數(shù)人云與大家分享了一篇關(guān)于Docker的反方言論——《一份Docker的反方辯論——我還是用Heroku好了》,一周之后,同樣的作者,又為Docker正名,寫了一篇正方言論。D...
摘要:在中,并沒有對(duì)抽象類和接口的支持。例如,當(dāng)對(duì)象需要對(duì)象的能力時(shí),可以有選擇地把對(duì)象的構(gòu)造器的原型指向?qū)ο螅瑥亩_(dá)到繼承的效果。本節(jié)內(nèi)容為設(shè)計(jì)模式與開發(fā)實(shí)踐第一章筆記。 動(dòng)態(tài)類型語言 編程語言按數(shù)據(jù)類型大體可以分為兩類:靜態(tài)類型語言與動(dòng)態(tài)類型語言。 靜態(tài)類型語言在編譯時(shí)已確定變量類型,動(dòng)態(tài)類型語言的變量類型要到程序運(yùn)行時(shí),待變量被賦值后,才具有某種類型。 而JavaScript是一門典型...
閱讀 1102·2021-11-15 18:00
閱讀 2816·2021-09-22 15:18
閱讀 1979·2021-09-04 16:45
閱讀 762·2019-08-30 15:55
閱讀 3872·2019-08-30 13:10
閱讀 1346·2019-08-30 11:06
閱讀 1994·2019-08-29 12:51
閱讀 2303·2019-08-26 13:55