首页 > 技术文章 > 面向对象三大特性之继承

jiangxianseng 2019-11-26 20:36 原文

面向对象之继承

1.什么是继承?

继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父类称之为基类或超类。

在Python中,一个子类可以继承多个父类。(面试可能会问)

在其它语言中,一个子类只能继承一个父类。

2.继承的作用?

减少代码的冗余。

3.如何实现继承?

  1. 先确认谁是子类,谁是父类。

  2. 在定义类子类时, 通过class 子类名(父类名):这种方式就可以继承父类的属性和方法。可以通过 :子类名.__ base __查看到父类是谁

# 父类
class Father1:
    x = 1
    pass


class Father2:
    pass


class Father3:
    pass


# 子类, 
class Sub(Father1, Father2, Father3):
    pass


# 子类.__bases__ 查看父类
print(Sub.__bases__)
print(Sub.x)

4.如何寻找继承关系,即如何确定父类子类

得先抽象,再继承

  • 抽取对象之间相似的部分,总结出类
  • 抽取类之间相似的部分,总结出父类。
# 父类  # 总结出所有子类的相似部分
class OldboyPeople:
    school = 'oldboy'
    country = 'China'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


# 老师类
class OldboyTeacher(OldboyPeople):
    # school = 'oldboy'                       # 相似的属性和方法可以抽取出来定义成父类
    # country = 'China'

    # def __init__(self, name, age, sex):
    #     self.name = name
    #     self.age = age
    #     self.sex = sex

    # 老师修改分数
    def change_score(self):
        print(f'老师 {self.name} 正在修改分数...')


# 学生类
class OldboyStudent(OldboyPeople):
    # school = 'oldboy'
    # country = 'China'
    #
    # def __init__(self, name, age, sex):
    #     self.name = name
    #     self.age = age
    #     self.sex = sex
    # 学生选择课程
    def choose_course(self):
        print(f'学生 {self.name} 正在选择课程...')

5.继承背景下对象属性的查找顺序

  • 1.先从对象自己的名称空间中查找
  • 2.对象中没有,从子类的名称空间中查找。
  • 3.子类中没有, 从父类的名称空间中查找,若父类没有,则会报错!
# 父类
class Goo:
    x = 10
    pass


# 子类
class Foo(Goo):
    x = 100
    pass


foo_obj = Foo()
foo_obj.x = 1000  # 注意,这不是修改子类的属性,而是在子类对象里面添加了新属性
print(foo_obj.x)
print('对象的名称空间: ', foo_obj.__dict__)
print('子类的名称空间: ', Foo.__dict__)
print('父类的名称空间: ', Goo.__dict__)

6.什么叫派生

派生: 指的是子类继承父类的属性与方法,并且派生出自己独有的属性与方法。 若子类中的方法名与父类的相同,优先用子类的。

# 父类
class Foo:
    def f1(self):
        print('from Foo.f1...')

    def f2(self):  # self ---> bar_obj
        print('from Foo.f2...')
       
# 子类
class Bar(Foo):

    # 重写
    def f1(self):
        print('from Bar.f1..')
    def func(self):
        print('from Bar.func...')


bar_obj = Bar()
bar_obj.f1()    # 输出结果:from Bar.f1..
bar_obj.func()  # 输出结果:from Bar.func...
bar_obj.f2()    # 输出结果:from Foo.f2...

# 查找方法的顺序也是先在对象中找,如果对象中没有,则会在子类中找如果子类中没有,则去父类中找,如果父类中没有,则报错


问题: 子类重写父类的__init__导致代码更加冗余
class OldboyPeople:
    school = 'oldboy'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


class OldboyTeacher(OldboyPeople):

    def __init__(self, name, age, sex, sal):#想派生出自己的属性,还需要重新写父类的属性,这样非常麻烦
        self.name = name
        self.age = age
        self.sex = sex
        self.sal = sal

    def change_score(self):
        print(f'老师 {self.name} 修改分数...')


class OldboyStudent(OldboyPeople):

    def __init__(self, name, age, sex, girl):
        self.name = name
        self.age = age
        self.sex = sex
        self.girl = girl

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')


tea1 = OldboyTeacher('tank', 17, 'male', 15000000)
stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(tea1.name, tea1.age, tea1.sex, tea1.sal)
print(stu1.name, stu1.age, stu1.sex, stu1.girl)


'''
解决问题: 子类重用父类的属性,并派生出新的属性。
    两种方式:
        1.直接引用父类的__init__为其传参,并添加子类的属性。
        2.通过super来指向父类的属性。
            - super()是一个特殊的类,调用super得到一个对象,该对象指向父类的名称空间。
            
        注意: 使用哪一种都可以,但不能两种方式混合使用。
'''


方式一:
class OldboyPeople:
    school = 'oldboy'

    def __init__(self, name, age, sex):  # self == tea1, name == 'tank', age == 17, sex == 'male'
        self.name = name
        self.age = age
        self.sex = sex


class OldboyTeacher(OldboyPeople):

    # tea1, 'tank', 17, 'male', 15000000
    def __init__(self, name, age, sex, sal):
        # self.name = name
        # self.age = age
        # self.sex = sex
        # 类调用类内部的__init__,只是一个普通函数
        # OldboyPeople.__init__(tea1, 'tank', 17, 'male')
        OldboyPeople.__init__(self, name, age, sex) # 在子类的.__init__中调用
        self.sal = sal # 这样只需要写添加的属性就可以了

    def change_score(self):
        print(f'老师 {self.name} 修改分数...')


class OldboyStudent(OldboyPeople):

    def __init__(self, name, age, sex, girl):
        OldboyPeople.__init__(self, name, age, sex)
        self.girl = girl

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')


tea1 = OldboyTeacher('tank', 17, 'male', 15000000) # 传参的时候别忘了
print(tea1.name, tea1.age, tea1.sex, tea1.sal)


stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)


# 方式二:
class OldboyPeople:
    school = 'oldboy'

    # self == tea1
    def __init__(self, name, age, sex):  # self == tea1, name == 'tank', age == 17, sex == 'male'
        self.name = name
        self.age = age
        self.sex = sex


class OldboyTeacher(OldboyPeople):

    # tea1, 'tank', 17, 'male', 15000000
    def __init__(self, name, age, sex, sal):
        # super() ---> 特殊的对象 ---> 对象.属性 ---> 父类的名称空间
        # 会将调用类传入的对象当做第一个参数传给__init__()
        super().__init__(name, age, sex)
        self.sal = sal

    def change_score(self):
        print(f'老师 {self.name} 修改分数...')


class OldboyStudent(OldboyPeople):

    def __init__(self, name, age, sex, girl):
        super().__init__(name, age, sex)
        self.girl = girl

    def choose_course(self):
        print(f'学生 {self.name} 选择课程...')


tea1 = OldboyTeacher('tank', 17, 'male', 15000000)
print(tea1.name, tea1.age, tea1.sex, tea1.sal)


stu1 = OldboyStudent('sean', 28, 'male', '凤姐')
print(stu1.name, stu1.age, stu1.sex, stu1.girl)
 

7.经典类和新式类(了解)

  • 新式类:

    1.凡是继承object的类或子孙类都是新式类。

    2.在python3中所有的类都默认继承object。

  • 经典类:

    1.在python2中才会有经典类与新式类之分。

    2.在python2中,凡是没有继承object的类,都是经典类。

class User(object): # 新式类
    pass

class Sub(User):
    pass


print(User.__dict__)

#输出结果:
{'__module__': '__main__', 'x': 10, '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}

print(object)

输出结果:
<class 'object'>


'''
在python3中提供了一个查找新式类查找顺序的内置方法.
    mro():类的内置方法,会把当前类的继承关系列出来。
'''

# 注意: super()会严格按照mro列表的顺序往后查找,mro列表的构造是通过c3线性算法来实现的
class A:
    def test(self):
        print('from A.test')
        super().est()


class B:
    def est(self):
        print('from B.test')


class C(A, B):
    pass


c = C()
# 检查super的继承顺序
print(C.mro())
# 输出结果:是一个继承顺序的列表
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

# 去A找,有的话打印,然后super又执行了test,根据mro中查找打印B类中test。
c.test()
'''
from A.test
from B.test
'''

多继承情况下造成 “钻石继承” 或“菱形继承”

mro的查找顺序:

  • 新式类:

    广度优先

  • 经典类:

    深度优先

推荐阅读