面向对象:继承派生

Posted neozheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象:继承派生相关的知识,希望对你有一定的参考价值。

继承:

继承是指类与类之间的关系,是一种“什么”是“什么”的关系。

继承的功能之一就是用来解决代码重用问题

继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,父类又可以称为基类或者超类,新建的类称为派生类或者子类

如下代码所示:

class ParentClass1:
    pass

class ParentClass2:
    pass

class SubClass1(ParentClass1):  # SubClass1 是 ParentClass1的子类,ParentClass1是SubClass1的父类(基类)
    pass

class Subclass2(ParentClass1,ParentClass2):  # SubClass继承了 ParentClass1和ParentClass1两个父类
    pass

# 查看继承 """ 可利用 .__bases__ 的方式查看其基类(父类)(元祖的形式) """ print(SubClass1.__bases__) print(Subclass2.__bases__) # 打印结果: # (<class \'__main__.ParentClass1\'>,) # (<class \'__main__.ParentClass1\'>, <class \'__main__.ParentClass2\'>)

 

 

抽象与继承(先抽象后继承):

抽象:即抽取类似或者说比较像的部分; 抽象最主要的作用是划分类别

继承:是基于抽象的结果,通过编程语言去实现它;继承肯定需要先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构

抽象只是分析和设计过程中,一个动作或者说一种技巧,通过抽象可以得到类。

 

如上一节创建两个英雄类的例子:

class Garen:
    camp = "Demacia"

    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Riven:
    camp = "Noxus"

    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack

#  Garen类是英雄类, Riven类也是英雄类, 所以可以把Garen和Riven提取出一个Hero类

# 如下代码所示

 

对上述代码稍作修改后并提取父类,如下:

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Garen(Hero):
    pass


class Riven(Hero):
    pass

garen = Garen("草丛伦",30,100)
print(garen.nickname,garen.attack,garen.life)   
""" 虽然Garen这个类下面没有代码,更没有__init__函数,但是Garen 继承了Hero这个类的属性 """  
"""关于属性查找优先顺序,以 garen.nickname 为例,它会先去garen的命名空间里面去找有没有 nickname,如果没有再去garen的父类(Garen类)里面去找,如果还没有,再去其父类的父类(Hero类)去找...,如果最后都就没找到就报错"""
print(garen.__dict__)
# 运行结果: 
#
草丛伦 30 100
#
{\'nickname\': \'草丛伦\', \'attack\': 30, \'life\': 100}

 所以,通过继承,子类能够重用父类的属性。

 

属性查找顺序:

# 情况1:
class Foo:
    def f1(self):
        print("from Foo.f1")

    def f2(self):
        print("from Foo.f2")
        self.f1()

class Bar(Foo):
    def f2(self):
        print("from Bar.f2")

b = Bar()
b.f2()   #  调用f2时,首先会去b的命名空间里面找,但是,Bar中都没有__init__函数,所以b里面肯定为空;b里面没有就继续往Bar里面找
print(b.__dict__)

# 打印结果:
# from Bar.f2
# {}


# 情况2:
class Foo:
    def f1(self):
        print("from Foo.f1")

    def f2(self):
        print("from Foo.f2")
        self.f1()  # 哪个对象调用f2,self就是谁;在这个例子中self代表b这个对象,所以这句代码代表的含义为: b.f1()

class Bar(Foo):
    def f1(self):
        print("from Bar.f2")

b = Bar()
b.f2()   # 同理,程序会现在b的命名空间里面查找、调用f2;没找到再往Bar里面去找;也没找到就往Foo里面去找;在Foo中找到后执行f2,此时self.f1()变成了b.f1(),b.f1()在执行时也是先在b里面找,然后在Bar里面找,所以self.f1()执行的是Bar里面的f1,而不是Foo中的


# 执行结果:
# from Foo.f2
# from Bar.f2

 派生:

  子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),而且,一旦重新定义类自己的属性且与父类重名,name调用新增的属性时,就以自己的为准了。

  在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数的功能,应该是调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值。

“指名道姓”式的调用(不依赖继承关系)

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Riven(Hero):
    camp=\'Noxus\'
    def __init__(self,nickname,attack,life,skin):
        Hero.__init__(self,nickname,attack,life) #为了减少重复代码,调用父类的__init__功能  # “指名道姓”式的调用,不依赖继承,同理需要写self
        self.skin=skin #新属性
    def attacking(self,enemy): #在自己这里定义新的attacking,不再使用父类的attacking,且不会影响父类
        Hero.attacking(self,enemy) #调用父类Hero中的attacking功能  # 这是“指名道姓”式的调用,这种调用不依赖于继承关系,即:没有继承关系也能调用 # 因为没有根据继承关系调用,且调用的是其他类(Hero类)中的函数,因为类在调用函数的时候需要传入self这个参数(即对象本身),所以应该写成 Hero.attack(self,enemy),即需要传self
        print(\'from riven\')
    def fly(self): #在自己这里定义新的
        print(\'%s is flying\' %self.nickname)

r1=Riven(\'锐雯雯\',57,200,\'比基尼\')
print(r1.__dict__)
r1.fly() 

print(r1.skin)

# 执行结果:
# {\'nickname\': \'锐雯雯\', \'attack\': 57, \'life\': 200, \'skin\': \'比基尼\'}
#
锐雯雯 is flying
#
比基尼

 

继承的实现原理:

经典类和新式类:

  1. 只有Python2中才分经典类和新式类,Python3中统一都是新式类

  2. 在Python2中,没有继承object类的类,以及该类的子类,都是经典类

  3. 在Python2中,继承object类的类,以及该类的子类,都是新式类

  4. 在Python3中,无论是否声明继承object,都默认继承object,即Python3中所有类均为新式类

(object类定义了所有类所共有的一些内置方法)

如果没有指定基类,Python的类会默认继承object类,object是所有Python类的基类,它提供了一些常见方法(如 __str__)的实现

 

继承的实现:对于你定义的每一个,Python会计算出一个方法解析顺序(MRO),这个MRO(元祖、列表)就是一个简单的所有基类的线性顺序列表(MRO全称 method resolution order) #  object has no attribute \'mro\'

继承时,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

MRO列表合并了所有的父类并遵守如下规则:

  1. 对象优先于类被查找,子类会优先于父类被查找

  2. 多个父类会根据他们在列表中的位置顺序被查找

  3. 如果对下一个类存在两个合法的选择,则选择第一个父类

Python中子类可以同时继承多个父类;如果继承了多个父类,那么属性的查找方式有两种:深度优先(经典类)和广度优先(新式类)

深度优先:

 

广度优先:

示例代码:

class A(object):
    def test(self):
        print(\'from A\')

class B(A):
    def test(self):
        print(\'from B\')

class C(A):
    def test(self):
        print(\'from C\')

class D(B):
    def test(self):
        print(\'from D\')

class E(C):
    def test(self):
        print(\'from E\')

class F(D,E):
    # def test(self):
    #     print(\'from F\')
    pass
f1=F()
f1.test()
print(F.__mro__) # .mro() :只有新式类才有这个属性可以查看线性列表,经典类没有这个属性

# 执行结果:
# from D
# (<class \'__main__.F\'>, <class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.E\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>)

# 新式类继承顺序:F->D->B->E->C->A
# 经典类继承顺序:F->D->B->A->E->C
# python3中统一都是新式类
# python2中才分新式类与经典类

 

在子类中重用父类的方法或属性,有两种方法:

  1. “指名道姓”式的调用,不依赖继承关系(此方式上面的代码已经写过)

  2. 利用super()方式,依赖继承关系

super()方式:

class Hero:
    def __init__(self,nickname,attack,life):
        self.nickname = nickname
        self.attack = attack
        self.life = life

    def attacking(self,enemy):
        enemy.life -= self.attack


class Riven(Hero):

    camp = "Noxus"
    def __init__(self,nickname,attack,life,skin):
        super(Riven,self).__init__(nickname,attack,life)  # super()方法,依赖继承关系 # .__init__函数无需再写self参数

        # 在Python3中可以简写为:
        # super().__init__(nickname,attack,life)

        self.skin = skin

    def attacking(self,enemy):
        super(Riven,self).attacking(enemy)  # 这是super()方法,此方法依赖于继承关系  # super(Riven,self): super中的第一个参数传子类自己的类名,第二个写成self, 这样能够得到一个特殊的对象(其父类Hero的一个对象),这个特殊的对象能够调用Hero类中的属性;super(Riven,self).attacking(enemy): .attacking(enemy)就是前面得到的那个父类的特殊对象在调用Hero中的attacking函数,因为是对象在调用函数,所以不需要再在函数里面传入self。(或者说不是在调用函数,而是在调用绑定方法)

        """
        super(Riven,self)是Python2的写法,在Python3中可以简写为:
        super().attacking(enemy)
        """

        print(\'from riven\')
    def fly(self):
        print(\'%s is flying\' %self.nickname)

class Garen(Hero):
    camp = "Demacia"


r1 = Riven(\'锐雯雯\', 50,80,"比基尼")
print(r1.__dict__)
g1 = Garen("草丛伦",30,100)

r1.attacking(g1)
print(g1.life)

# 运行结果:
# {\'nickname\': \'锐雯雯\', \'attack\': 50, \'life\': 80, \'skin\': \'比基尼\'}
# from riven
# 50

 

 super()的运行机制: 

  # super()只会沿着基于某个类产生的MRO列表,依次往后寻找

代码示例:

class A:
    def f1(self):
        print("form A")
        super().f1()

class B:
    def f1(self):
        print("from B")

class C(A,B):
    pass

# C.mro()的结果:
# [<class \'__main__.C\'>,
# <class \'__main__.A\'>,
# <class \'__main__.B\'>,
# <class \'object\'>]

c = C()
c.f1()

"""
运行过程分析:c = C()会生成一个基于C类的MRO列表,super()会根据这个基于C类继承关系产生的MRO列表依次查找需要调用的属性(so super()是基于继承关系的,因为继承关系能够产生一个有顺序的MRO列表)
 具体过程: 先在c这个对象中找有没有f1(),再往C这个类中找有没有f1();然后再往A中去找,找到了f1(),执行A的f1()后先打印“from A”,
然后,super().f1():由于此时程序scan MRO列表已经刚刚找完了<class \'__main__.A\'>,super()会接着这个基于C产生的MRO列表继续往后寻找f1()
然后再B中找到了f1(),执行B中的f1()
所以,虽然A没有继承B,但是super()只会沿着基于C的继承关系产生的MRO列表按顺序往后查询;即 super()不看它们的继承关系,只看基于谁产生的MRO列表顺序。即:只会参照C的MRO列表
"""


# 运行结果:
# form A
# from B

上述示例的另一种情况:(稍作修改)

1. A和B同时继承G:

class G:
    def f1(self):
        print("from G")

class A(G):
    def f1(self):
        print("form A")
        super().f1()

class B(G):
    def f1(self):
        print("from B")

class C(A,B):
    pass


c = C()
c.f1()
print(C.mro())

# 虽然A继承G,但super().f1()还是执行的B中的f1函数

2. A继承S,B不继承S

class S:
    def f1(self):
        print("from S")

class A(S):
    def f1(self):
        print("form A")
        super().f1()  

class B:
    def f1(self):
        print("from B")

class C(A,B):
    pass

c = C()
c.f1()
print(C.mro())

# 运行结果:
# form A
# from S
# [<class \'__main__.C\'>, <class \'__main__.A\'>, <class \'__main__.S\'>, <class \'__main__.B\'>, <class \'object\'>]  

# 分析:新式类:当A和B没有同时继承同一个父类时,先把A进行一遍广度优先,再把B进行一遍广度优先

 

以上是关于面向对象:继承派生的主要内容,如果未能解决你的问题,请参考以下文章

面向对象的继承和派生

python之旅:面向对象之继承与派生

面向对象:继承与派生

面向对象之继承与派生(day7)

面向对象-继承与派生

面向对象之继承与派生