日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網(wǎng)站首頁 編程語言 正文

python語法學習之super(),繼承與派生_python

作者:??Python編程學習圈???? ? 更新時間: 2022-07-27 編程語言

1 什么是繼承?

繼承是一種創(chuàng)建新類的方式;

在Python中,新建的類可以繼承一個或多個父類,新建的類可稱為子類或派生類,父類又可稱為基類或超類。

繼承可以用來解決類與類之間的代碼重用性問題;

class ParentClass1: #定義父類
    pass
class ParentClass2: #定義父類
    pass
class SubClass1(ParentClass1): #單繼承
    pass
class SubClass2(ParentClass1,ParentClass2): #多繼承
    pass

注意:

在python中一個子類可以繼承多個父類,在其他語言中,一個子類只能繼承一個父類;

python中的繼承分為單繼承和多繼承;

通過類的內(nèi)置屬性__bases__可以查看類繼承的所有父類

>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

在Python3中只有新式類,即使沒有顯式地繼承object,也會默認繼承該類。

2?繼承的規(guī)則

子類繼承父類的成員變量和成員方法:

  • 子類不繼承父類的構(gòu)造方法,能夠繼承父類的析構(gòu)方法
  • 子類不能刪除父類的成員,但可以重定義父類成員
  • 子類可以增加自己的成員

示例:

# python中子類繼承父類成員變量之間的取值邏輯
class Person():
    def __init__(self, name, age, sex):
        self.name = "jasn"
        self.age = '18'
        self.sex = sex
    def talk(self):
        print("i want to speak something to yo!!")
class Chinese(Person):
    def __init__(self, name, age, sex, language):
        Person.__init__(self, name, age, sex)  # 用父類的name,age,sex 覆蓋掉子類的屬性
        self.age = age  # 覆蓋掉了父類的age,取值為子類實例中傳入age參數(shù)
        self.language = "chinese"
    def talk(self):
        print("我說的是普通話!!")
        Person.talk(self)
obj = Chinese("nancy",'18','male',"普通話")
print(obj.name)  # 對應場景A
print(obj.age)  # 對應場景B
print(obj.language)  # 對應場景C
obj.talk()  # 對應場景D
# 總結(jié):
# A:若父類中初始化了成員變量,子類調(diào)用父類構(gòu)造方法未覆蓋屬性(self.name),則調(diào)用子類屬性時取值為父類中初始化的成員變量;
# B:若父類中初始化了成員變量,若子類調(diào)用父類構(gòu)造方法覆蓋屬性(self.age)則取值為子類實例中傳入?yún)?shù)
# C:若父類未初始化該成員變量,則無論子類中有無進行對父類構(gòu)造方法進行屬性的覆蓋,均取子類實例中傳入的參數(shù)
# D:對于方法,如果子類有這個方法則直接調(diào)用,如果子類沒有則去父類查找。父類沒有則報錯

3?繼承原理

對于你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表。

>>> D.mro() # 新式類內(nèi)置了mro方法可以查看線性列表的內(nèi)容,經(jīng)典類沒有該內(nèi)置方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止,而這個MRO列表的構(gòu)造是通過一個C3線性化算法來實現(xiàn)的。

實際上就是合并所有父類的MRO列表并遵循如下三條準則:

  • 子類會先于父類被檢查
  • 多個父類會根據(jù)它們在列表中的順序被檢查
  • 如果對下一個類存在兩個合法的選擇,選擇第一個父

Python 中的 MRO —— 方法搜索順序

Python中針對類提供了一個內(nèi)置屬性?mro?可以查看方法搜索順序

MRO 是 method resolution order,主要用于在多繼承時判斷方法、屬性 的調(diào)用路徑。

print(C.__mro__)  #C是多繼承后的類名

輸出結(jié)果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

在搜索方法時,是按照?mro?的輸出結(jié)果 從左至右 的順序查找的;

如果在當前類中找到方法,就直接執(zhí)行,不再搜索;如果沒有找到,就查找下一個類中是否有對應的方法,如果找到,就直接執(zhí)行,不再搜索。

如果找到最后一個類,還沒有找到方法,程序報錯。

4?多繼承屬性查詢順序

(1)多繼承結(jié)構(gòu)為菱形結(jié)構(gòu)

如果繼承關(guān)系為菱形結(jié)構(gòu),那么經(jīng)典類與新式類會有不同MRO。

箭頭表示搜索順序

(2)多繼承結(jié)構(gòu)為非菱形結(jié)構(gòu)

會按照先找B這一條分支,然后再找C這一條分支,最后找D這一條分支的順序直到找到我們想要的屬性。

5?查找流程

① 由對象發(fā)起的屬性查找,會從對象自身的屬性里檢索,沒有則會按照對象的類.mro()規(guī)定的順序依次找下去。

② 由類發(fā)起的屬性查找,會按照當前類.mro()規(guī)定的順序依次找下去。

主要知識點:類的__mro__ 屬性的用法;

屬性查找

有了繼承關(guān)系,對象在查找屬性時,先從對象自己的__dict__中找,如果沒有則去子類中找,然后再去父類中找.....

class Foo:
    def f1(self):
        print('Foo.f1')
    def f2(self):
        print('Foo.f2')
        self.f1() 

class Bar(Foo):
    def f1(self):
        print('Bar.f1')
b=Bar()
b.f2()
# 運行結(jié)果:
Foo.f2
Bar.f1
# 運行流程分析:
b.f2()會在父類Foo中找到f2,先打印Foo.f2,然后執(zhí)行到self.f1(),即b.f1(),仍會按照:對象本身->類Bar->父類Foo的順序依次找下去,在類Bar中找到f1,因而打印結(jié)果為Bar.f1

父類如果不想讓子類覆蓋自己的方法,可以采用雙下劃線開頭的方式將方法設置為私有的:

class Foo:
    def __f1(self): # 變形為_Foo__f1
        print('Foo.f1') 
    def f2(self):
        print('Foo.f2')
        self.__f1() # 變形為self._Foo__f1,然后回到Bar類中找,沒有,再到Foo中找到了
class Bar(Foo):
    def __f1(self): # 變形為_Bar__f1
        print('Bar.f1')
b=Bar() # Bar類處于執(zhí)行階段,_Bar__f1變?yōu)開_f1
b.f2() # 在父類中找到f2方法,進而調(diào)用b._Foo__f1()方法,是在父類中找到的f1方法

# 運行結(jié)果:
Foo.f2
Foo.f1

6?繼承概念的實現(xiàn)

方式主要有2類:

  • 實現(xiàn)繼承
  • 接口繼承

① 實現(xiàn)繼承是指使用基類的屬性和方法而無需額外編碼的能力。

② 接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現(xiàn)的能力(子類重構(gòu)爹類方法)。

在考慮使用繼承時,有一點需要注意,那就是兩個類之間的關(guān)系應該是“屬于”關(guān)系。

例如:Employee 是一個人,Manager 也是一個人,因此這兩個類都可以繼承 Person 類,但是 Leg 類卻不能繼承 Person 類,因為腿并不是一個人。

7?私有屬性私有方法在繼承中的表現(xiàn)

父類中的私有方法和私有屬性都是不能被子類繼承下來的;

測試示例:父類中的私有屬性和私有方法是否能被繼承下來?

class Perpon:
    num = 20
    __num1 = 12
    def __test1(self):
        print('__test1....')
    def test2(self):
        print('test2...')
class Student(Perpon):
    def test(self):
        print('num...')
        print(self.num)
        # print(Student.__num1)
        self.test2()
        # self.__test1()
 
student = Student()
student.test()
student.test2()
# student.__test1() # 報錯
'''
num...
20
test2...
test2...
'''

8?派生類

1)在父類的基礎上產(chǎn)生子類,產(chǎn)生的子類就叫做派生類

2)父類里沒有的方法,在子類中有了,這樣的方法就叫做派生方法。

3)父類里有,子類也有的方法,就叫做方法的重寫(就是把父類里的方法重寫了)

class Hero:
    def __init__(self, nickname,aggressivity,life_value):
        self.nickname = nickname
        self.aggressivity = aggressivity
        self.life_value = life_value
    def attack(self, enemy):
        print('Hero attack')
class Garen(Hero):
    camp = 'Demacia'
    def attack(self, enemy): #self=g1,enemy=r1
        # self.attack(enemy) #g1.attack(r1),這里相當于無限遞歸
        Hero.attack(self,enemy)  # 引用 父類的 attack,對象會去跑 父類的 attack
        print('from garen attack')  # 再回來這里
    def fire(self):
        print('%s is firing' % self.nickname)
class Riven(Hero):
    camp = 'Noxus'
g1 = Garen('garen', 18, 200)
r1 = Riven('rivren', 18, 200)
g1.attack(r1)
# print(g1.camp)
# print(r1.camp)
# g1.fire()

9?屬性的覆蓋(派生屬性)

子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類);

需要注意的是:一旦重新定義了自己的屬性且與父類重名,那么調(diào)用新增的屬性時,就以自己為準了(屬性的覆蓋)。

示例:

# 派生屬性: 子類中自己添加的新屬性
# 屬性的覆蓋: 子類和父類有相同屬性,調(diào)用自己的
class Perpon:
    num = 20
    def __init__(self, name):
        print('person...')
class Student(Perpon):
    num = 10  # 把父類中的20覆蓋
    def __init__(self, name, age):  # age 為派生屬性
        super().__init__(name)
        self.name = name
        self.age = age
        print('student...')
    def study(self):
        print(super().num)
        pass
student = Student('趙四', 23)
print(student.name) # person...
print(student.age)
print(student.num)
student.study()
'''
person...
student...
趙四
23
10
20
'''

10?父類屬性(方法)的重用

指名道姓的重用

class A:
    def __init__(self):
        print('A的構(gòu)造方法')
class B(A):
    def __init__(self):
        print('B的構(gòu)造方法')
        A.__init__(self)
class C(A):
    def __init__(self):
        print('C的構(gòu)造方法')
        A.__init__(self)
class D(B,C):
    def __init__(self):
        print('D的構(gòu)造方法')
        B.__init__(self)  # 先找到B,B調(diào)用A,等這個線性任務處理完之后,在繼續(xù)下一行代碼
        C.__init__(self)  # 先找到C,C里面也調(diào)用A的方法
    pass
f1=D()  #A.__init__被重復調(diào)用
'''
D的構(gòu)造方法
B的構(gòu)造方法
A的構(gòu)造方法
C的構(gòu)造方法
A的構(gòu)造方法
'''

Super()方法重用

class A:
    def __init__(self):
        print('A的構(gòu)造方法')
class B(A):
    def __init__(self):
        print('B的構(gòu)造方法')
        super(B,self).__init__()
class C(A):
    def __init__(self):
        print('C的構(gòu)造方法')
        super(C,self).__init__()
class D(B,C):
    def __init__(self):
        print('D的構(gòu)造方法')
        super().__init__()  # super(D,self).__init__()
f1=D() #super()會基于mro列表,往后找
'''
D的構(gòu)造方法
B的構(gòu)造方法
C的構(gòu)造方法
A的構(gòu)造方法
'''
# super() 語法
# super(type[, object-or-type])   type 當前類,object-or-type 為實例化對象,一般默認為self,不過該參數(shù)在python3中默認

super()是一個特殊的類,調(diào)用super得到一個對象,該對象指向父類的名稱空間。

派生與繼承解決問題:子類重用父類的屬性,并派生出新的屬性。

注意:使用哪一種都可以,但不能兩種方式混合使用!

11?繼承派生機制的作用

可以將一些共用的功能加在基類中,實現(xiàn)代碼的共享;

在不改變基類的基礎上改變原有的功能;

練習:

list類里只有append向末尾加一個元素的方法,但沒有向列表頭部添加元素的方法,試想能否為列表在不改變原有功能的基礎上添加一個inster_head(x)方法,此方法能在列表的前部添加元素?

class Mylist(list):
    def insert_head(self,x):
        # self.reverse()
        # self.append(x)
        # self.reverse()
        self.insert(0,x)  #直接在最開始插入x
myl = Mylist(range(3,6))
print(myl)      #[3.4.5]
myl.insert_head(2)
print(myl)      #[2,3,4,5]
myl.append(6)
print(myl)      #[2,3,4,5,6]

12?Super()

super(cls,obj)返回被綁定超類的實例(要求obj必須為cls類型的實例)

super() 返回被綁定超類的實例,等同于:super(class,實例方法的第一個參數(shù),必須在方法內(nèi)調(diào)用)

格式:

父類類名.方法名稱(self) 或者 super().方法名稱()或者super(本類類名,對象名)

作用:借助super()返回的實例間接調(diào)用父類的覆蓋方法;

示例:

#此示例示意用super函數(shù)間接調(diào)用父類的
class A:
    def work(self):
        print('A.work被調(diào)用')
class B(A):
    '''B類繼承子A類'''
    def work(self):
        print('B.work被調(diào)用')
    def super_work(self):
        #調(diào)用b類自己的work方法
        self.work()
        #調(diào)用父類的work
        super(B,self).work()
        super().work()  #此種調(diào)用方式只能在實例方法內(nèi)調(diào)用
b = B()
# b.work()            #B.work被調(diào)用!!
# super(B,b).work()   #A.work被調(diào)用
b.super_work()
# super_work()   #出錯

調(diào)用super()會得到一個特殊的對象,該對象專門用來引用父類的屬性,且繼承順序嚴格遵循mro繼承序列;

示例:

class Father1:
    x =10
    pass
class Father2:
    x = 20
    pass
#多繼承的情況下,從左到右
class Sub(Father1,Father2):
    def __init__(self):   #注意__int__不是__init__
        print(super().__delattr__)
print(Sub.mro())   # [<class '__main__.Sub'>, <class '__main__.Father1'>, <class '__main__.Father2'>, <class 'object'>]
obj = Sub()
print(object)   #<class 'object'>

mro():會把當前類的繼承關(guān)系列出來,嚴格按照mro列表的順序往后查找

class A:        #默認繼承object
    def test(self):
        print('from A.test')
        super().test()
class B:
    def test(self):
        print('from B.test')
class C(A, B):
    pass
c = C()
#檢查super的繼承順序
#mro(): 會把當前類的繼承關(guān)系列出來。
print(C.mro()) #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
c.test()    #from A.test
            #from B.test

使用super調(diào)用父類中的方法,注意分析程序的執(zhí)行順序。

class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)
        print('parent的init開始被調(diào)用')
        self.name = name
        print('parent的init結(jié)束被調(diào)用')
class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)
        print('Son1的init開始被調(diào)用')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)
        print('Son1的init結(jié)束被調(diào)用')
class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)
        print('Son2的init開始被調(diào)用')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)
        print('Son2的init結(jié)束被調(diào)用')
class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init開始被調(diào)用')
        # 多繼承時,相對于使用類名.__init__方法,要把每個父類全部寫一遍
        # 而super只用一句話,執(zhí)行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因
        # super(Grandson, self).__init__(name, age, gender) 效果和下面的一樣
        super().__init__(name, age, gender)
        print('Grandson的init結(jié)束被調(diào)用')
print(Grandson.__mro__) #搜索順序
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)
 
'''結(jié)果如下:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init開始被調(diào)用
Son1的init開始被調(diào)用
Son2的init開始被調(diào)用
parent的init開始被調(diào)用
parent的init結(jié)束被調(diào)用
Son2的init結(jié)束被調(diào)用
Son1的init結(jié)束被調(diào)用
Grandson的init結(jié)束被調(diào)用
姓名:grandson
年齡:12
性別:男
'''

注意:在上面模塊中,當在子類中通過super調(diào)用父類方法時,parent被執(zhí)行了1次。

super調(diào)用過程:上面gs初始化時,先執(zhí)行g(shù)randson中init方法, 其中的init有super調(diào)用,每執(zhí)行到一次super時,都會從__mro__方法元組中順序查找搜索。

所以先調(diào)用son1的init方法,在son1中又有super調(diào)用,這個時候就就根據(jù)__mro__表去調(diào)用son2的init,然后在son2中又有super調(diào)用,這個就根據(jù)mro表又去調(diào)用parent中的init,直到調(diào)用object中的init。

所以上面的打印結(jié)果如此,要仔細分析執(zhí)行過程。

重點提示:

  • 1)super().__init__相對于類名.init,在單繼承上用法基本無差
  • 2)但在多繼承上有區(qū)別,super方法能保證每個父類的方法只會執(zhí)行一次,而使用類名的方法會導致方法被執(zhí)行多次,具體看前面的輸出結(jié)果。
  • 3)多繼承時,使用super方法,對父類的傳參數(shù),應該是由于python中super的算法導致的原因,必須把參數(shù)全部傳遞,否則會報錯。
  • 4)單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的參數(shù),否則會報錯。
  • 5)多繼承時,相對于使用類名.__init__方法,要把每個父類全部寫一遍, 而使用super方法,只需寫一句話便執(zhí)行了全部父類的方法。

這也是為何多繼承需要全部傳參的一個原因

練習一

子類調(diào)用自己的方法的時候同時調(diào)用父類的方法

class Dog(Animal):
    def sleep(self):
        # 方法一 父類.方法名(對象)
        # Animal.eat(self)
        # 方法二 super(子類,對象名).方法名()
        super(Dog,dog).sleep()
        print('去狗窩')
    def look_door(self):
        print('看門狗')
dog = Dog('哈巴狗',23)
dog.sleep() 
'''
睡覺
去狗窩
'''

練習二

在類的外部調(diào)用super()

# 類外部調(diào)用super()
dog = Dog('哈巴狗', 23)
super(Dog, dog).sleep() # super(子類,對象名).方法名()
dog.sleep()
'''
睡覺
去狗窩
'''

原文鏈接:https://juejin.cn/post/7102609657428082719

欄目分類
最近更新