1. 程式人生 > >笨方法學Python習題 45: 物件、類、以及從屬關係

笨方法學Python習題 45: 物件、類、以及從屬關係

程式碼1:

## Animal is-a object (yes, sort of confusing) look at the extra credit
class Animal(object):
    pass

## Dog is-a class
class Dog(Animal):
    def __init__(self, name):

    ## Animal has-a object
    self.name = name

## Cat is-a class
class Cat(Animal):
    def __init__(self, name):
    ## Cat has-a object
self.name = name ## Person is-a class class Person(object): def __init__(self, name): ## Person has-a object self.name = name ## Person has-a pet of some kind self.pet = None ## Employee is-a class class Employee(Person): def __init__(self, name, salary): ## ?? hmm what is this strange magic?
super(Employee, self).__init__(name) ## Employee has-a object self.salary = salary ## Fish is-a object class Fish(object): pass ## Salmon is-a class class Salmon(Fish): pass ## Halibut is-a class class Halibut(Fish): Pass ## rover is-a Dog rover = Dog("Rover") ## satan is-a Cat
satan = Cat("Satan") ## mary is-a Person mary = Person("Mary") ## mary has-a pet mary.pet = satan ## frank is-a Employee frank = Employee("Frank", 120000) ## frank has-a pet frank.pet = rover ## flipper is-a Fish flipper = Fish() ## crouse is-a Salmon crouse = Salmon() ## harry is-a Halibut harry = Halibut()

加分習題
1. 研究一下為什麼 Python 添加了這個奇怪的叫做 object 的 class,它究竟有什麼
含義呢?

  1. 有沒有辦法把 Class 當作 Object 使用呢?
    把已經定義的基類就可以作為新定義class的object
    類似於:
    class A(object):
    pass
    class B(A):
    pass

  2. 在習題中為 animals、 fish、還有 people 新增一些函式,讓它們做一些事情。看看
    當函式在 Animal 這樣的“基類(base class)”裡和在 Dog 裡有什麼區別。

  3. 找些別人的程式碼,理清裡邊的“是啥”和“有啥”的關係。

  4. 使用列表和字典建立一些新的一對應多的“has-many”的關係。

  5. 你認為會有一種“has-many”的關係嗎?閱讀一下關於“多重繼承(multiple
    inheritance)”的資料,然後儘量避免這種用法。
    這裡寫圖片描述

多重繼承裡面容易出現重複呼叫的情況。比如說圖的右邊程式碼明顯會呼叫A和D兩次。為了避免這種情況,可以使用左邊的程式碼,參考註釋中標明change和new的部分。
他們的執行結果如下:
這裡寫圖片描述

MRO(類繼承的順序)我查了資料以後的發現super.init()函式並不是一定呼叫父類函式,它其實是一個繼承的順序標記。
搬運某個大神的理解“
super 其實幹的是這事
def super(cls, inst):
mro = inst.class.mro()
return mro[mro.index(cls) + 1]
引數 cls 和 inst 分別做了兩件事:
1. inst 負責生成 MRO 的 list
2. 通過 cls 定位當前 MRO 中的 index, 並返回 mro[index + 1]
這兩件事才是 super 的實質,一定要記住!
MRO 全稱 Method Resolution Order,它代表了類繼承的順序。
舉個例子

class Root(object):
    def __init__(self):
        print("this is Root")

class B(Root):
    def __init__(self):
        print("enter B")
        # print(self)  # this will print <__main__.D object at 0x...>
        super(B, self).__init__()
        print("leave B")

class C(Root):
    def __init__(self):
        print("enter C")
        super(C, self).__init__()
        print("leave C")

class D(B, C):
    pass

d = D()
print(d.__class__.__mro__)

輸出

enter B
enter C
this is Root
leave C
leave B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.Root'>, <type 'object'>)

知道了 super 和父類其實沒有實質關聯之後,我們就不難理解為什麼 enter B 下一句是 enter C 而不是 this is Root(如果認為 super 代表“呼叫父類的方法”,會想當然的認為下一句應該是this is Root)。流程如下,在 B 的 init 函式中:

super(B, self).init()
首先,我們獲取 self.class.mro,注意這裡的 self 是 D 的 instance 而不是 B 的

然後,通過 B 來定位 MRO 中的 index,並找到下一個。顯然 B 的下一個是 C。於是,我們呼叫 C 的 init,打出 enter C。

順便說一句為什麼 B 的 init 會被呼叫:因為 D 沒有定義 init,所以會在 MRO 中找下一個類,去檢視它有沒有定義 init,也就是去呼叫 B 的 init。”
其實這一切邏輯還是很清晰的,關鍵是理解 super 到底做了什麼。

於是,MRO 中類的順序到底是怎麼排的呢?Python’s super() considered super!中已經有很好的解釋,我翻譯一下:
在 MRO 中,基類永遠出現在派生類後面,如果有多個基類,基類的相對順序保持不變。
大神的文章地址:https://laike9m.com/blog/li-jie-python-super,70/