物件導向(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列表
類的屬性的查詢順序
新式類中的菱形繼承
新式類中的查詢順序
類的屬性查詢順序:
新式類:先找自身,再先深度找,如果有共同父類再廣度找(直接看類的mro列表就知道查詢順序了 類.mro() )
經典類: python2中的經典類就是深度優先
# 此段程式碼指定時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上傳部落格哦,如有不好還請見諒~