继承与派生

继承

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

在编程世界中继承表示类与类之间资源的从属关系。子类可以继承父类的所有属性和方法。

class Parent1:  # <==> Parent1(object)
    pass

class Parent2:
    pass

# 单继承:一个子类只能继承一个父类
class Sub1(Parent1):
    pass

'''python中,支持多继承'''
class Sub2(Parent1, Parent2):
    pass

通过类的内置属性__bases__可以查看类继承的所有父类

print(Sub1.__bases__)  # (<class '__main__.Parent1'>,)
print(Sub2.__bases__)  # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
print(Parent1.__bases__)  # (<class 'object'>,)

在Python2中有经典类与新式类之分,没有显式地继承object类的类,以及该类的子类,都是经典类,显式地继承object的类,以及该类的子类,都是新式类。而在Python3中,即使没有显式地继承object,也会默认继承该类,如下

print(Parent1.__bases__)  
# > (<class 'object'>,)

因而在Python3中统一都是新式类,关于经典类与新式类的区别,我们稍后讨论

提示:object类提供了一些常用内置方法的实现,如用来在打印对象时返回字符串的内置方法__str__

以后我们在定义类的时候 如果没有其他明确的父类 也可能习惯写object兼容(为了兼容python2)

class Student(object):
  pass

继承与抽象

要找出类与类之间的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类,如下图所示

基于抽象的结果,我们就找到了继承关系

基于上图我们可以看出类与类之间的继承指的是什么‘是’什么的关系(比如人类,猪类,猴类都是动物类)。子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题。比如我们定义以下两个类:

class Student():

    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        self.name = name
        self.age = age
        self.gender = gender
        self.courses = course

    def choose_course(self):
        pass

class Teacher():
    def __init__(self, name, age, gender, level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
    def score(self):
        pass

类Teacher与Student之间存在重复的代码,老师与学生都是人类,所以我们可以得出如下继承关系,实现代码重用

class People():
    school = 'SH'

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


class Student(People):
    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        # 调用__init__方法即可

        # 调用方法一:
        # People(name, age, gender)  # 类名(),实例化,即调用People中的__init__函数

        # 调用方法二:
        People.__init__(self, name, age, gender)  # 类名.方法名(四个参数!)
        '''类来调用绑定给对象的方法,网上找找'''

        self.courses = course

    def choose_course(self):
        pass

class Teacher(People):
    def __init__(self, name, age, gender, level):
        People.__init__(self, name, age, gender)
        self.level = level

    def score(self):
        pass

Teacher类内并没有定义__init__方法,但是会从父类中找到__init__,因而仍然可以正常实例化,如下

stu = Student('kevin', 20, 'male')
print(stu.name)  # kevin
print(stu.age)  # 20
print(stu.gender)  # male
print(stu.school)  # SH

tea = Teacher('ly', 18, 'male', 10)
print(tea.name)  # ly
print(tea.age)  # 18
print(tea.gender)  # male
print(tea.level)  # 10
print(tea.school)  # SH

总结

继承本质应该分为两部分

  1. 抽象:将多个类相同的东西抽出去形成一个新的类
  2. 继承:将多个类继承刚刚抽取出来的新的类
"""
对象:数据与功能的结合体
类(子类):多个对象相同数据和功能的结合体
父类:多个类(子类)相同数据和功能结合体
ps:类与父类本质都是为了节省代码,都是给对象提供方便
"""

属性查找

有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……

单继承下的属性查找

class Foo():
    pass

class Bar(Foo):
    pass

class Func(Bar):
    pass

先从对象自己的名称空间中查找,找不到在从产生这个对象的类中查找,类中找不到在去继承的父类中查找,一直找到没有父类为止,在找不到就报错了
1.注意:一定要分清楚self是谁

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

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

obj = Bar() 
obj.f2() 
# > from Foo.f2
# > from Bar.f1
"""
    强调:对象点名字 永远从对象自身开始一步步查找
    以后在看到self.名字的时候 一定要搞清楚self指代的是哪个对象
"""

分析:Bar加括号表示实例化这个类得到一个对象,对象点方法名表示对象调方法,先去对象自己的名称空间查找,然后去产生这个对象的类(Bar)中查找,没有。去父类中查找,找见了。
self.f1(),此时的self是obj对象,此时self.f1()=>obj.f1(),又重新开始寻找,所以,又运行的是Bar类中的f1方法
2.隐藏属性

class Foo():
    def __f1(self):  # _Foo__f1()
        print('from Foo.f1')
    def f2(self):  
        print('from Foo.f2')
        self.__f1()  # self._Foo__f1()

class Bar(Foo):
    def __f1(self):  # _Bar__f1()
        print('from Bar.f1')

obj = Bar()
obj.f2()
# > from Foo.f2
# > from Foo.f1

与上面不一样,隐藏属性在定义阶段就已经变形了,在哪个类中隐藏的就用的哪个的类名

这次是指名道姓的调用

这也体现了,隐藏对外不对内

多继承下的属性查找

多继承分为非菱形结构和菱形结构。菱形结构中经典类与新式类会有不同,分别对应属性的两种查找方式:深度优先和广度优先

非菱形查找

参照下述代码,多继承结构为非菱形结构,此时,会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

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

class F:
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

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

class A(B, C, D):
    # def test(self):
    #     print('from A')
    pass

print(A.mro())
'''
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
'''

obj = A()
obj.test() # 结果为:from B
# 可依次注释上述类中的方法test来进行验证

深度优先

当类是经典类时,多继承情况下,在要查找属性不存在时,会按照深度优先的方式查找下去(Python2中)

class G: # 在python2中,未继承object的类及其子类,都是经典类
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试

广度优先

当类是新式类时,多继承情况下,在要查找的属性不存在时,会按照广度优先的方式查找下去

class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证

super关键字与mro列表

派生方法

super关键字:子类调用父类的方法

  1. python2的写法:super(Student, self).init(name, age, gender)
  2. python3的写法:super().init(name, age, gender)
    在以前学习中,使用类名点方法名,指名道姓的调用方法,有几个参数传几个参数。

使用这种方式,可以不继承父类

'''但是,Student实例化的对象想要调用school属性,必须依赖于继承'''
class People():
    school = 'SH'
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class Student(People):
    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        # People(name, age, gender)
        # 指名道姓的调用方法
        People.__init__(self, name, age, gender)  # 这样的调用方式,其实是不依赖于继承的
        self.courses = course
    def choose_course(self):
        pass
stu = Student('kevin', 20, 'male')
print(stu.name)
print(stu.age)
print(stu.gender)

我们还可以直接使用super,拓展性更强

'''super(Student, self) 返回的是一个特殊对象(特殊之处后续讲解),它一定是个对象'''
class People():
    school = 'SH'
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

class Student(People):
    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        '''使用这种方式,比上面那种要强大'''
        '''super(Student, self) 返回的是一个特殊对象,它一定是个对象'''
        # 对象调方法,会把对象自己当成第一个参数传给self,所以只需要传3个参数
        super(Student, self).__init__(name, age, gender)  # 不能说继承的就是父类,不严谨
        # 可以认为是People类里面刚好有
        self.courses = course

    def choose_course(self):
        pass

stu = Student('kevin', 20, 'male')
print(stu.name)  # kevin
print(stu.age)  # 20
print(stu.gender)  # male

多继承中如果存在super关键字,可以直接打印mro()列表来判断寻找顺序,要打印起始类的mro()列表
没有super,就按照多继承的顺序查找即可

# 打印mro()列表:
print(Student.mro())
#  > [<class '__main__.Student'>, <class '__main__.People'>, <class 'object'>]

派生方法案例

案例1:
想要对一个方法有限制时。可以使用super(),先继承父类的方法,在子类中也定义这个方法,用super().方法名(父类方法参数),继承父类中的方法,然后在super()上面增加一些限制

class MyList(list):
    def append(self, values):
        if values == 'jason':
            print('jason不能尾部追加')
            return
        super().append(values)

obj = MyList()
print(obj, type(obj))
obj.append(111)
obj.append(222)
obj.append(333)
obj.append('jason')
print(obj)

案例2:把以下字典序列化
d = {
't1': datetime.date.today(),
't2': datetime.datetime.today(),
't3': 'jason'
}

import json
import datetime

d = {
    't1': datetime.date.today(),
    't2': datetime.datetime.today(),
    't3': 'jason'
}
print(d)
# > {'t1': 2023-03-23, 't2':2023-03-23 14:29:15.803020, 't3': 'jason'}

# res = json.dumps(d)
# print(res)  # 会报错
"""
序列化报错
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type date is not JSON serializable
"""

d不能被序列化的原因:外面是字典可以被序列化,但里面的t1,t2不能被序列化

import json
json.JSONEncoder
"""
能够被序列化的数据是有限的>>>:里里外外都必须是下列左边的类型
    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+
"""

想要能够被序列化:
1.转换方式1:手动转类型(简单粗暴)

d = {
    't1': str(datetime.date.today()),
    't2': str(datetime.datetime.today())
}
res = json.dumps(d)
print(res)

2.转换方式2:派生方法(儒雅高端)
可以使用面向对象

"""
查看dumps源码 注意cls=None参数 默认传JsonEncoder
查看该类的源码 发现default方法是报错的发起者
编写类继承JsonEncoder并重写default方法 之后调用dumps手动传cls=我们自己写的类
"""
class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):
        """
        :param o: 接收无法被序列化的数据
        :return: 返回可以被序列化的数据
        """
        # 增加额外的功能
        if isinstance(o, datetime.datetime):  # 判断是否是datetime类型 如果是则处理成可以被序列化的类型
            return o.strftime('%Y-%m-%d %X')
        elif isinstance(o, datetime.date):
            return o.strftime('%Y-%m-%d')
        # 继承的原来的功能
        return super().default(o)  # 最后还是调用原来的方法 防止有一些额外操作没有做


res = json.dumps(d, cls=MyJsonEncoder)
print(res)

mro列表

python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下

print(Student.mro())
#  > [<class '__main__.Student'>, <class '__main__.People'>, <class 'object'>]

python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

所以对象名.属性名的查找顺序是,先从对象本身的属性里,没有找到,则参照属性查找的发起者(即对象)所处类的MRO列表来依次检索。

1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去

案例:

class A:
    def test(self):
        super().test()

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

class C(A, B):
    pass

c = C()
c.test()
# > from B
'''查看一个类的mro列表, 要从起始类开始看.是从哪个类开始的'''
print(C.mro())  # 要查看C类的mro列表  # 类名.mro()
# > [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

如果类A与类B换了位置

class C(B, A):
    pass

c = C()
c.test()
print(C.mro()) # 先从C里面找,没有,去B找,找见了,就直接返回,碰不到super()关键字
# > [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

c = A()
print(A.mro())
# > <class '__main__.A'>, <class 'object'>]
c.test()  # 会报错
# > 'super' object has no attribute 'test'

组合

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例

class Foo():
    def __init__(self, m):
        self.m = m

class Bar():
    def __init__(self, n):
        self.n = n

obj = Foo(10)
obj1 = Bar(20)
'''这就是组合,此时的obj对象已经变成了超级对象,可以通过一个对象得到另外一个对象的值'''
obj.x = obj1  # 对象增加一个属性:对象名.属性名 (赋值)= 另一个对象名
# 想要输出n值
# print(obj1.n)  # 方法一,如果不使用这种方法呢
# 方法二:
# obj.x是obj1,obj1.n就是求对象的属性
print(obj.x.n)  # 20

案例

# 继承是满足什么是什么的关系才用
# 组合是满足什么有什么的关系才用
class People():  # 父类
    school = 'SH'

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

# 加一门课程
class Course():
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price
python=Course('python', '6mon', 30000)  # 产生一个课程对象
linux=Course('linux', '5mon', 20000)
# 求对象的任意属性
# print(python.name)  
# print(python.price)
# print(python.period)
# print(linux.name)
# print(linux.price)
'''以上是产生了一门课程,怎么让学生拥有一门课程呢?'''

# 管理员
class Admin(People):  # 继承People类
    pass  # 继承是满足什么是什么的关系才用


class Student(People):
    def __init__(self, name, age, gender, course=None):
        if course is None:
            course = []
        # python2中的写法
        # super(Student, self).__init__(name, age, gender)  
        # python3中的写法
        super().__init__(name, age, gender)
        self.courses = course
    def choose_course(self):
        pass

stu = Student('kevin',20, 'female')
'''一门课程'''
stu.course = python  # 组合

'''多门课程,可以用列表'''
stu.courses.append(python.name)  # 组合
stu.courses.append(linux.name)
print(stu.courses)  # [python对象,linux对象]
# > [<__main__.Course object at 0x00000215378C9B00>, <__main__.Course object at 0x00000215378C9BA8>]
for course in stu.courses:  # 查看对象
    print(course)
    
stu.courses.append(python.name) # 也可以直接将课程对象的名字存入列表中
stu.courses.append(linux.name)
print(stu.courses)
# > ['python', 'linux']

# for course_obj in stu.courses:  # 使用循环
#     print(course_obj.name)  # 查看对象名字


class Teacher(People):
    def __init__(self, name, age, gender, level):
        super().__init__(name, age, gender)
        self.level = level
    def score(self):
        pass

tea = Teacher('ly', 10, 'male',10)

tea.course = python  # 拥有一门课程
print(tea.course.name)
print(tea.course.period)
print(tea.course.price)

posted @ 2023-03-26 15:54  星空看海  阅读(29)  评论(0编辑  收藏  举报