天天看點

python學習之面向對象(二)

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 練習

場景模拟:模拟英雄聯盟的遊戲人物

  1. 建立一個 Game_role的類.
  2. 構造方法中給對象封裝name,ad(攻擊力),hp(血量).三個屬性.
  3. 建立一個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算法

表頭:清單的第一個元素

表尾:表頭以外的所有元素

python學習之面向對象(二)

解題過程:

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