python物件導向的繼承-組合-02

suwanbin發表於2019-07-25

物件導向(OOP)的三大特徵:# 封裝、繼承、多型

繼承

什麼是繼承

繼承:# 是一種關係,描述兩個物件之間什麼是什麼的什麼的關係

例如:麥兜、佩奇、豬豬俠、豬剛鬣,都是豬

為什麼要使用繼承

繼承的好處:# 繼承的一方可以直接使用被繼承一方已經有的東西

程式中,繼承描述的是類和類之間的關係

​ 例如:a繼承了b,a就能直接使用b已經存在的方法和屬性

​ 此時,a稱之為子類,b稱之為父類,也稱之為基類。

為什麼使用繼承:# 其目的是為了重用已經有了的程式碼,提高重用性

如何使用繼承

語法

class 類名稱(父類的名稱):

# 在python中 一個子類可以同時繼承多個父類

繼承小案例(子類直接用父類的方法,無需自己實現)

class Base:
    desc = "這是一個基類"

    def show_info(self):
        print(self.des)

    @staticmethod
    def make_money():
        print("一天賺ta一個億")


class SubClass:
    @staticmethod
    def make_money():
        print("一天賺ta一百")
    pass


class SubClass2(Base):
    # 通過繼承使用父類的 make_money
    pass


# 無繼承
obj1 = SubClass()
obj1.make_money()
# 一天賺ta一百

# 繼承,可得到父類的方法及屬性
obj2 = SubClass2()
obj2.make_money()
# 一天賺ta一個億
print(obj2.desc)
# 這是一個基類

管理學生與老師小案例(老師類預設有教書的方法,而學生類是不可以有的,所以不能直接讓學生類繼承老師類)

# 需求:管理老師
class Teacher:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def say_hi(self):
        print(f"name:{self.name},gender:{self.gender},age:{self.age}")


t1 = Teacher('jack', 'male', 20)
t1.say_hi()
# name:jack,gender:20,age:male


# 擴充套件需求:把老師也一起管理
class Student:
    def __init__(self, name, age, gender, number):
        self.name = name
        self.age = age
        self.gender = gender
        self.number = number

    def say_hi(self):
        print(f"name:{self.name},gender:{self.gender},age:{self.age}")


s1 = Student('sushan', 'female', 18, 'xxx01')
s1.say_hi()
# name:sushan,gender:18,age:female

上面程式碼有些重複,學生和老師有很多屬性都是一樣的。

抽象

直意:不具體、不清晰、很模糊、看不太懂

程式設計中:# 將多個子類的中相同的部分,進行抽取,形成一個新的類,這個過程也稱之為抽象

# 抽取老師學生的共同特徵,然後再繼承
class Person:
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

    def say_hi(self):
        print(f"name:{self.name},gender:{self.gender},age:{self.age}")

    pass


class Teacher(Person):

    def teaching(self):
        print("老師教學生,寫程式碼....")


class Student(Person):

    pass


t1 = Teacher('jack', 'male', 20)
t1.say_hi()
# name:jack,gender:20,age:male
t1.teaching()
# 老師教學生,寫程式碼....

s1 = Student('rose', 'female', 20)
s1.say_hi()
# name:rose,gender:20,age:female
# s1.teaching()  # 報錯,找不到teaching(他沒有,他的父類也沒有)

如何正確使用繼承:

  • 1.先抽象(提取特徵)再繼承

  • 2.繼承一個已經現存的類,擴充套件或是修改原始的功能

class A:
    text = 'haha'


class B(A):
    text = 'heihei'  # 註釋掉訪問父級的
    pass


b = B()
print(b.text)  # b自身沒有,找類,就不用訪問類的父類的了
# heihei

b.text = 'xixix'
print(b.text)  # b(物件)自身有,就不能找類了
# xixix

屬性的查詢順序

查詢順序:物件自身 --> 類 --> 父類 --> ...父類的上級父類... --> Object --> 報錯

派生與覆蓋(重寫)

  • 派生# 當一個子類中出現了與父類中不同的內容時,這個子類就稱之為派生類
class Person:
    @staticmethod
    def say_hi():
        print("hello")
        

# 這個Student子類不是派生,父類Person一模一樣。(這樣沒啥意思)
class Student(Person):
    pass

通常子類都會寫一些新的程式碼,不可能和父類完全一樣,即通常子類都是派生類

派生類就是子類的意思

  • 覆蓋# 也稱之為重寫(overrides)當子類出現了與父類名稱完全一樣的屬性或是方法,就是覆蓋
class Person:
    @staticmethod
    def say_hi():
        print("hello")


# 這個Student子類不是派生,父類Person一模一樣。(這樣沒啥意思)
class Student(Person):
    @staticmethod
    def say_hi():  # 與父類的say_hi 重複,重寫、覆蓋
        print("hello world!")
    pass


s = Student()
s.say_hi()
# hello world!

練習:實現一個可以限制元素型別的容器(子類訪問父類中的內容)

補充知識點

​ 子類訪問父類的方法:# super(當前類名稱, self).你要呼叫的父類的屬性或方法

# 小練習:做一個可以限制元素型別的容器型別
class MyList(list):  # 繼承list,可以直接用list的一些方法屬性
    def __init__(self, data_type):
        super(MyList, self).__init__()  # 應規範,子類重寫父類方法的時候__init__初始化函式中要呼叫父類的__init__初始化函式
        self.data_type = data_type

    def append(self, obj):
        '''
        重寫父類的append方法
        :param obj: 是要儲存的元素
        :return: None
        '''
        if isinstance(obj, self.data_type):
        # if type(obj) == self.data_type:  # 寫法二
            super(MyList, self).append(obj)  # 這裡需要訪問父類的append 方法來完成真正的儲存操作
        else:
            print(f"非指定型別{self.data_type}!")


# 建立時指定要儲存的元素型別
str_list = MyList(str)
str_list.append('abc')
print(str_list[0])
# abc
str_list.append(1)
# 非指定型別<class 'str'>!

訪問父類屬性的三種方式

# 1.super(類, 物件自身).類的屬性/方法
    python2的寫法(相容寫法,python2、3都可以用)
# 2.super().類的屬性/方法
    python3的新語法  ***** (推薦,python2專案慎用哦)
# 3.類.屬性/方法
    沒啥實際意義,不是繼承,這是直接用類來呼叫了

程式碼案例

# 子類訪問父類中的屬性
class Parent:
    text = 'abc'

    @staticmethod
    def say_something():
        print("anything")


class Sub(Parent):
    def show_info(self):
        # # 方式一: python2 和 3 都相容
        print(super(Sub, self).text)
        super(Sub, self).say_something()
        #
        # # 方式二:python 3 中的語法   *** 推薦
        print(super().text)
        super().say_something()
        #
        # # 方式三:沒啥意義,不是繼承,指名道姓的呼叫
        print(Parent.text)
        Parent.say_something()
        pass


s = Sub()
s.show_info()
# ----- 方式一
# abc
# anything
# ----- 方式二
# abc
# anything
# ----- 方式三
# abc
# anything

強調點

​ 如果子類繼承了一個現有的類,並且覆蓋了父類的__init__方法時,那麼必須在__init__方法中的第一行必須呼叫父類中的__init__方法,並傳入父類所需的引數。 --- 這是重點 ---

上面案例改版(沒有呼叫父類的__init__方法,父類可能沒有初始化完成,後續可能會導致一些意想不到的問題)

class Person:
    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age
        self.say_hello()  # 初始化時要呼叫的函式

    def say_hi(self):
        print(f"name:{self.name},gender:{self.gender},age:{self.age}")

    def say_hello(self):
        print(f"Hello, i'm {self.name}")


class Student:
    def __init__(self, name, gender, age, number):
        self.name = name
        self.gender = gender
        self.age = age
        self.number = number

    def say_hi(self):
        print(f"name:{self.name},gender:{self.gender},age:{self.age}")
        print(f"number:{self.number}")


# 上述程式碼優點冗餘,怎麼簡化?
class Student2(Person):
    def __init__(self, name, gender, age, number):
        super().__init__(name, gender, age)  # 不呼叫父類的__init__方法就會使父類的初始化函式中的say_hello方法,初始化就不能算是完成 ***
        self.number = number

    def say_hi(self):
        super().say_hi()
        print(f"number:{self.number}")


stu = Student2("rose", 'female', 18, 'young1')
# Hello, i'm rose
stu.say_hi()
# name:rose,gender:female,age:18
# number:young1

組合

組合:# 也是一種關係,描述的是兩個物件之間是什麼有什麼的關係,將一個物件作為另一個物件的屬性(即什麼有什麼)

例如:學生有手機、遊戲中的角色擁有某些裝備

組合無處不在,資料型別、函式都是物件,都有組合

組合的目的:# 重用現有程式碼

# 讓學生使用手機打電話、發簡訊
class Phone:
    def __init__(self, price, kind, color):
        self.price = price
        self.kind = kind
        self.color = color

    @staticmethod
    def call():
        print("正在呼叫xxx...")

    @staticmethod
    def send_msg():
        print("正在傳送....")


class Student:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def show_info(self):
        print(f"name:{self.name}, gender:{self.gender}")


# 讓學生擁有打電話這個功能(有聯絡)
stu1 = Student('rose', 'female')
phone1 = Phone(1888, 'vivo', 'red')
phone1.call()
# 正在呼叫xxx...

# 組合:把一個物件作為另一個物件的屬性
class Student2:
    def __init__(self, name, gender, phone):
        self.name = name
        self.gender = gender
        self.phone = phone

    def show_info(self):
        print(f"name:{self.name}, gender:{self.gender}")

phone2 = Phone(1888, 'vivo', 'red')
stu2 = Student2('rose', 'female', phone2)

stu2.phone.call()
# 正在呼叫xxx...
stu2.phone.send_msg()
# 正在傳送....

組合與繼承的取捨

'''
    繼承:分析兩個類的關係,到底是不是:什麼是什麼的關係
    組合:如果兩個類之間,沒有太大的關係,完全不屬於同類
    
    另外:組合相比繼承,耦合度更低
'''

菱形繼承(瞭解)

多繼承帶來的問題:python支援多繼承,雖然靈活,但會帶來名稱衝突的問題(到底找誰的)

新式類與經典類

python3 中任何類都是直接或間接繼承自object

新式類:任何顯式或隱式地繼承自object的類就稱之為新式類(即python3 中的類全是新式類)

經典類:不是object的子類,僅在python2 中出現

擴充套件

# 在python2 中可能有這樣子的程式碼
class Person(object):  # 預設讓python2 中的類也是新式類,相容寫法
    pass

mro列表(只在python3 中有)

呼叫方式:# 類.mro() --> 可以獲取到類的 **mro 列表**,裡面的元素就是類的查詢順序

class Parent:
    pass


class Sub(Parent):
    pass

print(Sub.mro())
# [<class '__main__.Sub'>, <class '__main__.Parent'>, <class 'object'>]
# 從左到右就是這個類的查詢順序,先Sub自身 再Parent 再object

​ 當使用super()函式時,python3會在mro列表上繼續搜尋下一個類。如果每個重定義的方法統一使用super()並只呼叫它一次,那麼控制流最終會遍歷完整個mro列表,每個方法也只會被呼叫一次

注意注意注意:使用super呼叫的所有屬性,都是從mro列表當前的位置往後找,千萬不要通過看程式碼去找繼承關係,一定要看mro列表

類的屬性的查詢順序

新式類中的菱形繼承

python物件導向的繼承-組合-02

新式類中的查詢順序

python物件導向的繼承-組合-02

類的屬性查詢順序:

新式類:先找自身,再先深度找,如果有共同父類再廣度找(直接看類的mro列表就知道查詢順序了 類.mro() )

python物件導向的繼承-組合-02

經典類: python2中的經典類就是深度優先

python物件導向的繼承-組合-02

# 此段程式碼指定時python2 執行
# 註釋掉不同類中的num 來測試查詢順序
class B:
    # num = 2
    pass


class C:
    # num = 3
    pass


class E(B):
    # num = 5
    pass


class F(C):
    # num = 6
    pass


class G(C):
    num = 7
    pass


class H(E, F, G):
    # num = 8
    pass

print(H.num)
# print(H.mro())  # python2 中沒有 mro()
# [<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.C'>, <class 'object'>]
# [H, E, B, F, G, C, object]  ---> 上面的mro簡化表示順序(這是python3 的順序)
# [H, E, B, F, C, G, object]  ---> 這是python2 的順序

初次用markdown上傳部落格哦,如有不好還請見諒~

相關文章