老玩家迴歸:
掛一張目前的陣容程序員
哎, 菜是原罪啊。。。。。。算法
下面咱們正式建立本身的類, 這裏咱們使用Python自定義某米賽爾號的精靈, 代碼以下:編程
class Elf: def setName(self, name): self.name = name def getName(self): return self.name def getInfo(self): return self
類的定義就像函數定義, 用 class 語句替代了 def 語句, 一樣須要執行 class 的整段代碼這個類纔會生效。進入類定義部分後, 會建立出一個新的局部做用域, 後面定義的類的數據屬性和方法都是屬於此做用域的局部變量。上面建立的類很簡單, 只有一些簡單的方法。當捕捉精靈後, 首先要爲其起名字, 因此咱們先編寫函數 setName() 和 getName()。彷佛函數中 self 參數有點奇怪, 咱們嘗試創建具體的對象來探究該參數的做用。函數
>>> 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>
建立對象和調用一個函數很類似, 使用類名做爲關鍵字建立一個類的對象, 實際上, Elf() 的括號裏是能夠有參數的, 後面咱們會討論到。咱們有兩隻精靈, 一隻是小火猴, 一隻是皮皮, 而且對他們執行 getName() , 名字正確返回。觀察 getInfo() 的輸出, 返回的是包含地址的具體對象信息, 能夠看到兩個對象的地址, 是不同的。Python 中的self做用和 C++ 中的 *this 指針相似, 在調用 精靈對象 的 setName() 和 getName() 函數時, 函數都會自動把該對象的地址做爲第一個參數傳入(該信息包含在參數 self 中), 這就是爲何咱們調用函數時不須要寫 self , 而在函數定義時須要把參數做爲第一個參數。傳入對象地址是至關必要的, 若是不傳入地址, 程序就不知道要訪問類的哪個對象。測試
類的每一個對象都會有各自的數據屬性, Elf 類中有數據屬性 name, 這是經過setName() 函數中的語句 self.name = name建立的。這個語句中的兩個 name 是不同的, 它們的做用域不同。第一個 name 經過 self 語句聲明的做用域是類 Elf() 的做用域, 將其做爲對象 x 的數據屬性進行存儲, 然後面的 name 的做用域是函數的局部做用域, 與參數中的 name 相同。然後面 getName() 函數返回的是對象中的 name。this
從更深層邏輯去說, 咱們捕捉到精靈的那一刻應該就有名字, 而並不是捕捉後去設置。因此這裏咱們須要的是一個初始化手段。Python中的__init__() 方法用於初始化類的實例對象。__init__() 函數的做用必定程度上與C++的構造函數類似, 但並不等於。C++ 的構造函數是使用該函數去建立一個類的實例對象, 而Python執行__init__() 方法時實例對象已被構造出來。__init__()方法會在對象構造出來後自動執行, 因此能夠用於初始化咱們所須要的數據屬性。修改Elf 類的代碼, 代碼以下:spa
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
在此處咱們增長了幾個數據的屬性: 性別、等級、能力和精靈屬性。連同前面的名字, 都放在__init__()方法進行初始化。數據屬性是可使用任意數據類型的, 小火猴屬性是火, 而精靈可能會有倆個屬性, 假設小火猴通過兩次進化稱爲烈焰猩猩屬性爲地面, 火系。爲了保持數據類型的一致性, 因此咱們使用元組存儲, 並讓小火猴的第二個屬性爲 None。因爲小火猴的屬性是固定的, 因此在__init__() 的輸入參數不須要 type。而精靈的能力會隨着等級的不一樣而不一樣, 因此在初始化中也須要實現這一點。咱們建立實例對象測試代碼:指針
>>> x = Elf('小火猴', 'male', 5) >>> y = Elf('皮皮', 'female', 6) # 這裏有錯誤, 皮皮不是火系, 能夠接着往下看, 思考如何改正 >>> 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]
這時候建立對象就須要參數了, 實際上這是__init__() 函數的參數。__init__() 自動將數據屬性進行了初始化, 而後調用相關函數可以返回咱們須要的對象的數據屬性。code
1.方法引用
類的方法和對象的方法是同樣的, 咱們在定義類的方時程序沒有爲類的方法分配內存, 而在建立具體實例對象的程序纔會爲對象的每一個數據屬性和方法分配內存, 咱們已經知道定義類的方法是 def 定義的, 具體定義格式與普通函數類似, 只不過類的方法的第一個參數要爲 self 參數。咱們能夠用普通函數實現對對象函數的引用:對象
>>> x = Elf('小火猴', 'male', 5) >>> getStatus1 = x.getStatus >>> getStatus1() [20, 10, 10, 10, 10, 10]
雖然看上去彷佛是調用了一個普通函數, 可是 getStatus1() 這個函數是引用 x.getStatus() 的, 意味着程序仍是隱性地加入了 self 參數。
2.私有化
先敲代碼:
>>> x.type ('fire', None) >>> x.getType() ('fire', None)
雖然這樣彷佛很方便, 但違反了類的封裝原則。對象的狀態對於類外部應該是不能夠訪問的。爲什麼要這樣作, 咱們查看Python 的模塊源碼時會發現源碼裏定義了不少類, 模塊中算法經過使用類是很常見的, 若是咱們使用算法時能隨意訪問對象中的數據屬性, 那麼頗有可能在不經意間修改算法中已經調好的參數, 這是十分尷尬的。儘管咱們不會能夠那麼去作, 但這種無心的改動是常有的事。通常封裝好的類都會有足夠的函數接口供程序員用, 程序員沒有必要訪問對象的具體數據類型。
爲防止程序員無心間修改了對象的狀態, 咱們須要對類的數據屬性和方法進行私有化。Python 不支持直接私有方式, 但可使用一些小技巧達到私有特性的目的。爲了讓方法的數據屬性或方法變爲私有, 只須要在它的名字前面加上雙下劃線便可, 修改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每級增長2點, 其他增長1點 def __test(self): pass
>>> x = Elf('小火猴', 'male', 5) >>> print(x.type) Traceback (most recent call last): File "seer.py", line 25, in <module> print(x.type) AttributeError: 'Elf' object has no attribute 'type'
>>> print(x.getName()) 小火猴 >>> x.test() Traceback (most recent call last): File "ser.py", line 28, in <module> x.test() AttributeError: 'Elf' object has no attribute 'test'
如今在程序外部直接訪問私有數據是不容許的, 咱們只能經過設定好的節後函數去調取對象信息。不過經過雙下劃綫實現的私有其實是"僞私有化", 實際上咱們仍是能夠作到從外部訪問這些私有屬性。
>>> print(x._Elf__type) ('fire', None)
Python 使用的是一種 name_mangling 技術, 將__membername 替換成 _class__membername, 在外部使用原來的私有成員時, 會提示沒法找到, 而上面執行的 x.Elf__type 是能夠訪問。簡而言之, 確保其餘人沒法訪問對象的方法和數據屬性是不可能的, 可是使用這種 name_mangling 技術是一種程序員不該該從外部訪問這些私有成員的強有力信號。
能夠看到代碼中還增長了一個函數level_up(), 這個函數用於處理精靈升級是能力的提高, 咱們不該該在外部修改 x 對象的 status , 因此應準備好接口去處理能力發生變化的情景, 函數 level_up() 僅僅是一個簡單的例子, 而聽說在工業代碼中, 這樣的函數接口是大量的, 程序須要對它們進行歸類並附上相應的文檔說明。
3.迭代器
Python容器對象(列表、元組、字典和字符串等)均可以能夠用 for 遍歷,
for element in [1, 2, 3]: print(element)
這種風格十分簡潔, for 語句在容器對象上調用了 iter(), 該函數返回一個定義了 next() 方法的迭代器對象, 它在容器中逐一訪問元素。當容器遍歷完畢, __next__() 找不到後續元素時, next() 找不到後續元素時, next()會引起一個 StopIteration 異常, 告知for循環終止。
>>> L = [1, 2, 3] >>> it = iter(L) >>> it <list_iterator object at 0x0302C530> >>> it.__next__() 1 >>> it.__next__() 2 >>> it.__next__() 2 >>> it.__next__() 3 >>> it.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
當知道迭代器協議背後的機制後, 咱們即可以吧迭代器加入到本身的類中。咱們須要定義一個__iter__()方法, 它返回一個有 next() 方法的對象, 若是類定義了next(), __iter__()能夠只返回self, 再次修改類 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] 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('名字 屬性 性別 等級 能力') return self def next(self): if self.__index == len(self.__info) - 1: raise StopIteration self.__index += 1 return self.__info[self.__index]
面向對象編程的好處之一就是代碼的複用, 實現這種重用的方法之一就是經過繼承機制。繼承是兩個類或多個類之間的父子關係, 子類繼承了基類的全部公有數據屬性和方法, 而且能夠經過編寫子類的代碼擴充子類的功能。能夠說, 若是人類能夠作到兒女繼承了父母的全部才學並加以拓展, 那麼人類的發展至少是如今的數萬倍。繼承實現了數據屬性和方法的重用, 減小了代碼的冗餘度。
那麼咱們如何實現繼承呢??若是咱們須要的類中具備公共的成員, 且具備必定的遞進關係, 那麼就可使用繼承, 且讓結構最簡單的類做爲基類。通常來講, 子類是父類的特殊化, 以下關係:
哺乳類動物 ————> 貓科動物 ————> 東北虎
東北虎類 繼承 貓科動物類, 貓科動物類 繼承 哺乳動物類, 貓科動物類編寫了全部貓科動物公有的行爲的方法而特定貓類則增長了該貓科動物特有的行爲。不過繼承也有必定弊端, 可能基類對於子類也有必定特殊的地方, 若是某種特定貓科動物不具備絕大多數貓科動物的行爲, 當程序員嗎,沒有理清類之間的關係是, 可能使子類具備不應有的方法。另外, 若是繼承鏈太長的話, 任何一點小的變化都會引發一連串變化, 咱們使用的繼承要注意控制繼承鏈的規模。
繼承語法: class 子類名(基類名1, 基類名2,...), 基類卸載括號裏, 若是有多個基類, 則所有寫在括號裏, 這種狀況稱爲多繼承。在Python中繼承有如下一些特色:
1) 在繼承中積累初始化方法__init__()函數不會被自動調用。若是但願子類調用基類的__init__() 方法, 須要在子類的 __init__() 方法中顯示調用它。這與C++差異很大。
2) 在調用基類的方法時, 須要加上基類的類名前綴, 且帶上 self 參數變量, 注意在類中調用該類中定義的方法時不須要self參數。
3) Python老是首先查找對應類的方法, 若是在子類中沒有對應的方法, Python纔會在繼承鏈的基類中按順序查找。
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('名字 屬性 性別 等級 能力') 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, 將精靈共有的行爲都放到基類中, 子類僅僅須要向基類傳輸數據屬性便可。這樣就能夠很輕鬆地定義其餘基於pokemon類的子類。由於某米賽爾號精靈有數千只, 使用繼承的方法能夠大大減小代碼量, 且當須要對所有精靈進行總體改變時僅需改變pokemanl類的__init__()便可, 並向基類傳輸數據, 這裏注意要加self參數, Elf 類沒有繼承基類的私有數據屬性, 所以在子類只有一個self.__type, 不會出現因繼承所形成的重名狀況。爲了能更加清晰地描述這個問題, 這裏再舉一個例子:
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, in <module> a_dog.print2() File "seer.py", line 11, in print2 print(self.__age) AttributeError: 'dog' object has no attribute '_dog__age'