6.2 類的空間角度研究類
6.2.1 添加對象屬性
【總結】對象的屬性不僅可以在
__init__
裡面添加,還可以在類的其他方法或者類的外面添加。
class A:
address = '召喚師峽谷'
def __init__(self, name):
self.name = name
def func(self,type):
self.type = type
# 類的外部可以添加
obj = A('蓋倫')
obj.age = 20
print(obj.__dict__)
# 在類的内部可以添加
obj.func('戰士')
print(obj.__dict__)
6.2.2 添加類的屬性
【總結】類的屬性添加方式也是比較靈活的,不僅可以在類内部添加,還可以在類的外部添加。
class A:
aaa = 'aaa'
def __init__(self,name):
self.name = name
def func(self):
A.ccc = 'ccc'
# 在類的外部可以添加
A.bbb = 'bbb'
print(A.__dict__)
# 在類的内部可以添加
A.func(1)
print(A.__dict__ )
6.2.3 對象如何查找類的屬性
對象在實體化的空間内,有一個指向類位址空間的指針
對象查找順序:先從對象空間找--> 類的空間找--> 父類空間找.....
類名查找順序:先從本類空間找--> 父類空間.....
6.3 類與類的關系
python中類與類之間的關系:
依賴關系
組合關系
繼承關系
6.3.1 依賴關系
将一個類名或者類的對象傳給另一個類的方法中
模拟一個場景,淘金者要進入OnePeace,淘金者要念口号讓大門打開,進入進入OnePeace後要念口号,然後門關上。
class Fossicker:
def __init__(self,name):
self.name = name
def ask_open(self,se):
print(f'{self.name}大喊:“芝麻開門”')
re.open_door() #調用SecretPath裡的open_door方法
print(f"{self.name}進入了{se.name}裡面")
def ask_close(self,se):
print(f'{self.name}又說:“芝麻關門”')
re.closs_door() #調用SecretPath裡的closs_door方法
print(f"{self.name}把{se.name}的門關了")
class SecretPath:
def __init__(self,name):
self.name = name
def open_door(self):
print('門開了')
def closs_door(self):
print('門關了')
e1 = Fossicker('阿加')
se1 = SecretPath('OnePeace')
e1.ask_open(se1)
e1.ask_close(se1)
上述場景,Fossicker的這個類中用到了SecretPath的對象,而SecretPath的對象并沒有用到其他類的任何方法或是屬性,我們可以說Fossicker類依賴SecretPath類,二者構成依賴關系。
6.3.2 組合關系
将一個類的對象封裝到另一個類的對象的屬性中
情景模拟:相親
class Boy:
def __init__(self,name):
self.name = name
def meet(self,gril):
if gril.age <= 25:
self.grilfriend = gril
print(f'{self.name}跟{self.grilfriend.name}很是合得來')
gril.boyfriend = self
else:
self.grilfriend = None
print('不合适,沒成')
def have_dinner(self):
if self.grilfriend:
print(f"{self.name}請{self.grilfriend.name}吃大餐")
else:
print('算了回家吃泡面吧')
class Gril:
def __init__(self,name,age):
self.name = name
self.age = age
def shopping(self):
print(f"{self.name}和{self.boyfriend.name}一起去購物")
gailun = Boy('蓋倫')
nvjing = Gril('凱特琳',24)
gailun.meet(nvjing)
gailun.have_dinner()
nvjing.shopping()
【總結】不管是依賴關系還是組合關系,在類與類之間進行互相調用時,我們希望的是,某些情況下不要把所有的因素寫死,而是擷取到其他對象的整個空間内的資訊,是以在傳參時可以,應注意這一點
6.3.3 練習
場景模拟:模拟英雄聯盟的遊戲人物
- 建立一個 Game_role的類.
- 構造方法中給對象封裝name,ad(攻擊力),hp(血量).三個屬性.
-
建立一個attack方法,此方法是執行個體化兩個對象,互相攻擊的功能:
例: 執行個體化一個對象 蓋倫,ad為10, hp為100
執行個體化另個一個對象 劍豪 ad為20, hp為80
蓋倫通過attack方法攻擊劍豪,此方法要完成 '誰攻擊誰,誰掉了多少血, 還剩多少血'的提示功能
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
def attack(self,p1):
p1.hp = p1.hp - self.ad
print(f'{self.name}攻擊{p1.name},{p1.name}掉了{self.ad}血,還剩{p1.hp}血')
gailun = GameRole('蓋倫',10,100)
jianhao = GameRole('劍豪',20,80)
gailun.attack(jianhao) #依賴關系的應用
更新:建立一個武器類,讓對象借助武器進行攻擊
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
class Weapon:
def __init__(self, name, ad):
self.name = name
self.ad = ad
def weapon_attack(self, p1, p2):
# print(self)
p2.hp = p2.hp - self.ad
print(f'{p1.name}利用{self.name}給了{p2.name}一下,{p2.name}還剩{p2.hp}血')
gailun = GameRole('蓋倫', 10, 100)
jianhao = GameRole('劍豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('長槍', 40)
great_sword.weapon_attack(gailun, jianhao) #此時進行攻擊動作的執行者是great_sword
此時進行攻擊動作的執行者是great_sword,而我們希望的是GameRole的對象使用武器進行攻擊,下面進行優化
class GameRole:
def __init__(self, name, ad, hp):
self.name = name
self.ad = ad
self.hp = hp
def equip_weapon(self,wea): #
self.weapon = wea
class Weapon:
def __init__(self, name, ad):
self.name = name
self.ad = ad
def weapon_attack(self, p1, p2):
p2.hp = p2.hp - self.ad
print(f'{p1.name}利用{self.name}給了{p2.name}一下,{p2.name}還剩{p2.hp}血')
gailun = GameRole('蓋倫', 10, 100)
jianhao = GameRole('劍豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('長槍', 40)
gailun.equip_weapon(great_sword) #依賴關系
gailun.weapon.weapon_attack(gailun,jianhao)
6.4 繼承
面向對象的三大特性:封裝,繼承,多态
6.4.1 繼承的初識
B類繼承A類,B就叫做A的子類,派生類,子類可以調用父類的屬性和方法
class Person:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
看上面的代碼,建立了三個類,出現了大量的重複代碼,利用繼承對這些代碼進行整合,如下:
# 繼承
class Animal:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Person(Animal):
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
6.4.2 繼承的特性
繼承的優點:
- 節省代碼
- 增強代碼的耦合性
- 代碼更加規範
- 重構父類的屬性或方法
繼承的缺點:
- 降低了代碼的靈活性
- 過多的耦合性,使得在父類的常量、變量和方法被修改時,需要考慮子類的修改,甚至導緻大段的代碼需要重構
6.4.3 單繼承
隻有一個父類(基類,超類)
子類以及對象可以調用父類的屬性方法
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要進食')
class Person(Animal):
pass
# 從類名執行父類的屬性
print(Person.__dict__)
print(Person.live)
對象執行父類的一切
實體化對象首先執行
__init__
,自己沒有就去父類找,根據
__init__
函數傳入相應的參數
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要進食')
class Person(Animal):
pass
# 從對象執行父類的一切
p1 = Person('蓋倫',24,'男')
print(p1.__dict__)
p1.eat()
【注意】子類以及子類的對象隻能調用父類的屬性和方法,不能進行操作(增删改)。
子類可以重寫父類的方法
就是在子類的空間内,建立一個新的與父類函數重名的函數
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要進食')
class Person(Animal):
def eat(self):
print('人要吃飯')
p1 = Person('蓋倫',24,'男')
p1.eat()
【注意】如遇子類屬性與父類重名的話,先從本身進行執行,父類不執行
如何執行子類方法有執行父類方法
一種是無綁定的調用父類的函數
一種是使用super自動綁頂
class Animal:
live = '有生命的'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('需要進食')
class Person(Animal):
# 方法一:
def __init__(self,name,age,sex,type):
Animal.__init__(self,name,age,sex) #調用父類的方法,需要手動傳入self
self.type = type
def eat(self):
print('人要吃飯')
p1 = Person('蓋倫',24,'男','戰士')
print(p1.__dict__)
# 方法二:super方法
class Person(Animal):
def __init__(self,name,age,sex,type):
# super(Person, self).__init__(name,age,sex) #super第一個括号裡的内容可以省略,
super().__init__(name,age,sex)
self.type = type
def eat(self):
print('人要吃飯')
p1 = Person('蓋倫',24,'男','戰士')
print(p1.__dict__)
# super可以繼承父類的方法
class Person(Animal):
def __init__(self,name,age,sex,type):
# super(Person, self).__init__(name,age,sex)
super().__init__(name,age,sex) #super第一個括号的内容可以省略
self.type = type
def eat(self):
print('人要吃飯')
super().eat()
p1 = Person('蓋倫',24,'男','戰士')
p1.eat()
6.4.4 多繼承
一個子類繼承了多個父類,難點在于繼承的順序。
面向對象:python2.2之前,都是經典類;python2.2-2.7之間,存在兩種類型,一種是經典類另一種是新式類;python3隻有新式類。
經典類:基類不繼承object,查詢規則滿足深度優先原則
新式類:基類繼承object,查詢規則滿足mro算法,不是單純的廣度優先
mro算法
表頭:清單的第一個元素
表尾:表頭以外的所有元素
解題過程:
1.先從上到下,列出每個類的線性表達式.
2.列出的線性表達式中如果有多個表達式的話,需要進行化簡.
3.最終的子類也使用線性表達式表示,由于有多個父類,也需要進行化簡.
線性表達式有兩個部分組成:第一個類名稱為頭部,剩餘的部分稱為尾部.
先從上到下求出每個類的線性表達式,期間需要化簡
G = [GO]
F = F + [GO] = [FGO]
D = D + [GO] = [DGO]
B = B + [FGO] = [BFGO]
C = C + [E] = [CE]
再求最終的類的線性表達式,需要化簡
A = [BFGO] + [CE] + [DGO]
AB = [FGO] + [CE] + [DGO]
ABF = [GO] + [CE] + [DGO]
由于第一個表達式的G是第三個表達式的尾部,是以不能抽取,則從第二個表達式的頭部開始抽取C
ABFC = [GO] + [E] + [DGO]
再次從第一個表達式的頭部開始抽取,依然不能抽取,則從第二個表達式抽取
ABFCE = [GO] + [DGO]
第一個表達式的頭部依然不能抽取,從第二個表達式的頭部開始抽取
ABFCED = [GO] + [GO]
剩下的部分相同了,就可以直接寫
ABFCEDGO # 最終的mro解析順序
【注意】在實際項目中,檢視mro順序可以使用 類名.mro來擷取類的查找順序
6.5 封裝和多态
6.5.1 封裝
給對象封裝屬性或方法的過程
通過對象或者self擷取被封裝的屬性方法
6.5.2 多态
python預設支援多态
python 設定變量時,不需要設定資料類型
6.5.3 鴨子類型
俗話你看起來像鴨子,那麼你就是鴨子
兩個互相獨立的A,B兩個類,但内部功能相似,python一般會将類似于A,B裡面的相似功能的命名設定成相同。
舉例:A,B雖然沒有聯系,預設使用一種規範,使用起來更友善。像是清單裡的index與字元串的index,兩個index沒有關系,但是使用的時候比較友善。
6.6 super
super(類名,self).function() 執行本類的父類的方法
類名:多繼承中,嚴格按照self的從屬類的mro順序執行,即執行本類名的父類裡的方法;單繼承中,按照父類的方法執行
class A:
def func(self):
print('in A')
class B(A):
def func(self):
print('in B')
class C(A):
def func(self):
print('in C')
class D(B,C):
def func(self):
print('in D')
super(D,self).func()
t = D()
t.func()
#輸出
in D
in B
# 改變一下class D的super參數
class D(B,C):
def func(self):
print('in D')
super(B,self).func()
t = D()
t.func()
輸出
in D
in C #按照mro順序執行下一父類的方法
6.7 類的限制
限制是對類的限制。
場景模拟:設計一個戰士和刺客的英雄類型
class Zhanshi:
def type_role(self,name):
self.name = name
print(f"{self.name}是一個戰士")
class Cike:
def type_role(self, name):
self.name = name
print(f"{self.name}是一個刺客")
gailun = Zhanshi()
# g.type_role('戰士')
jianhao = Cike()
# j.type_role('刺客')
# 設計一個規範化的接口
def type_role(obj,role): #歸一化設計
obj.type_role(role)
type_role(gailun,'蓋倫')
type_role(jianhao,'劍豪')
當增加一個法師的類型
# 在上述的基礎上增加新的英雄屬性
class Fashi:
def type_role(self,name):
self.name = name
print(f"{self.name}是一個法師")
moganna = Fashi()
type_role(moganna,'莫甘娜') #調用規範化接口使用
如果我要按照歸一化設計的規範繼續增加新的英雄類型時,那麼怎麼讓程式設計者知道要使用這一規範呢?
目前有兩種方式:1.在父類建立一種限制;2.模拟抽象類的概念,建立一種強制限制
第一種方式
在父類建議限制,主動抛出錯誤,非強制性
class Hero:
def type_role(self,name): #定義一種規範,子類要定義type_role方法
raise Exception('請定義type_role方法')
class Zhanshi(Hero):
def type_role(self,name):
self.name = name
print(f"{self.name}是一個戰士")
class Cike(Hero):
def type_role(self, name):
self.name = name
print(f"{self.name}是一個刺客")
class Fashi(Hero):
def type_ro(self,name): #沒有定義type_role而是定義了一個type_ro
self.name = name
print(f"{self.name}是一個法師")
pass
def type_role(obj,role): #歸一化設計
obj.type_role(role)
gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()
type_role(moganna,'莫甘娜') #調用接口函數時,主動報錯:raise Exception('請定義type_role方法')
第二種方式
類似于抽象類的概念,定義一種強制限制,你隻要是我的派生類,那麼這個方法必須要有。
from abc import ABCMeta,abstractmethod
class 父類名(metaclass=ABCMeta):
@abstractmethod
def 函數名
from abc import ABCMeta,abstractmethod
class Hero(metaclass=ABCMeta):
@abstractmethod
def type_role(self,name): #定義一種規範,子類要定義type_role方法
pass
class Zhanshi(Hero):
def type_role(self,name):
self.name = name
print(f"{self.name}是一個戰士")
class Cike(Hero):
def type_role(self, name):
self.name = name
print(f"{self.name}是一個刺客")
class Fashi(Hero):
def type_ro(self,name):
self.name = name
print(f"{self.name}是一個法師")
pass
gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()
moganna.type_ro('莫甘娜')
# 函數執行前,直接報錯TypeError: Can't instantiate abstract class Fashi with abstract methods type_role
轉載于:https://www.cnblogs.com/jjzz1234/p/11164525.html