今日内容概要
-
继承(面向对象中得核心)
-
单继承 多继承
-
-
单继承下的属性查找
-
多继承下的属性查找
-
super和mro的使用
-
多态和鸭子类型
继承(核心)
面向对象的三大特征:封装、继承、多态
1.什么是继承
继承就是一种新建类的方式,新建出来的类我们称为‘子类或者叫派生类’,被继承的类我们称之为是‘父类或者基类’
当然出来的类子类可以遗传父类的所有属性
2.为什么要用继承
类解决了什么问题:对象与对象之间的代码冗余问题
继承解决了什么问题:类与类之间的代码冗余问题
3.怎么使用继承
经典类:没有继承object类的子子孙孙类都是经典类
新式类:继承了object类的子子孙孙类都是新式类
“
只有在python2中才区分经典类和新式类,如果是python3的版本,所有类都是新式类,也就是说在python3中默认的类都是继承了object类,在python3中没有了经典类和新式类的说法了
”
class Parent1:
pass
class Parent2(object):
pass
”Sub1继承了Parent1的类,Sub1就称为是子类,Parent1类就称为是父类或者叫基类“
单继承:一个类只继承了一个类,一个类继承了两个或者两个以上的类就是多继承了
class Sub1(Parent1):
pass
多继承的类:Sub2就是子类,Parent1和Parent2都是父类
class Sub2(Parent1, Parent2):
pass
print(Sub1.__bases__) # (<class '__main__.Parent1'>,)
print(Sub2.__bases__) # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
继承的实际案例
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.__init__(self, name, age, gender) # 指名道姓的调用方法
self.course = course
def choose_course(self):
print('%s 选课成功, %s' % (self.name, self.course))
stu = Student('ly', 20, 'male')
print(stu.school) # SH 属性的查找
print(stu.name)
print(stu.age)
print(stu.gender)
class Teacher(People):
def __init__(self, name, age, gender, level):
People.__init__(self, name, age, gender)
self.level = level
def score(self, stu_name):
print('%s 得了%s分' % (stu_name, 10))
tea = Teacher('kevin', 19, 'female')
print(tea.name)
print(tea.age)
print(tea.gender)
属性查找
有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找.......
>>> class Foo:
... def f1(self):
... print('Foo.f1')
... def f2(self):
... print('Foo.f2')
... self.f1()
...
>>> class Bar(Foo):
... def f1(self):
... print('Foo.f1')
...
>>> b=Bar()
>>> b.f2()
Foo.f2
Foo.f1
b.f2()会在父类Foo中找到f2,先打印Foo.f2,然后执行到self.f1(),即b.f1(),仍会按照:对象->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为Foo.f1
父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
>>> class Foo:
... def __f1(self): # 变形为_Foo__fa
... print('Foo.f1')
... def f2(self):
... print('Foo.f2')
... self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
...
>>> class Bar(Foo):
... def __f1(self): # 变形为_Bar__f1
... print('Foo.f1')
...
>>>
>>> b=Bar()
>>> b.f2() #在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
Foo.f2
Foo.f1
继承的实现原理
菱形问题
大多数面向对象语言都不支持多继承,而在python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻
A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。
这种继承结构下导致的问题称之为菱形问题:如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?如下所示
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,C):
pass
obj = D()
obj.test() # 结果为:from B
要想搞明白obj.test()是如何找到方法test的,需要了解python的继承实现原理
继承原理
python到底是如何实现继承的呢?对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有的基类的线性顺序列表如下
>>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
所以obj.test()的查找顺序是,先从对象obj本身的属性里找到方法test,没有找到,则参照属性查找的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后再B中找到方法test
1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
深度优先和广度优先
参照下述代码,多继承结构为非菱形结构,此时,会按照先找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来进行验证
如果继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先
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的使用
#A没有继承B,但是A内super会基于C.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
多态和鸭子类型
多态它是面向对象的三大特征之一
什么是多态? 同一种事物的多种形态
举例:
水:气态水、液态水、固态水 动物:人 鸡 狗 猪等都是动物的一种形态
之所以说人、鸡、狗、猪是动物,是因为他们具备动物的特征,speak功能
import abc # abstract class 抽象类 具体的Specific
class Animal(metaclass=abc.ABCMeta): # 把animal类变成了抽象类
"""父类中得方法不是为了实现逻辑的,实现功能的,而是单纯的为了限制子类的行为"""
@abc.abstractmethod # 把抽象类中得方法变成抽象方法, 它不实现具体的功能,就是单纯的为了限制子类中的方法
def speak(self):
pass
@abc.abstractmethod
def jiao(self):
pass
"""抽象类和普通类有什么区别? 抽象类只能够被继承、不能够被实例化"""
# Animal() # Can't instantiate abstract class Animal with abstract methods speak
"""怎么限制子类People类必须有speak功能? 我们可以在父类中来限制子类的行为,其实就是限制子类中必须有某些方法"""
"""Python崇尚的是鸭子类型"""
"""鸭子类型就是更多的关注的是对象的行为,而不是对象的类型"""
class People(Animal):
def speak(self):pass
# print('from People.speak')
def jiao(self):
pass
class Dog(Animal):
def speak(self):
pass
class Pig(Animal):
def speak(self):
pass
"""多态带来的特性:在不考虑对象类型的情况下,直接调用对象的方法或者属性"""
def animal(obj):
return obj.speak()
animal(obj)
animal(obj1)
animal(obj2)
"""面试题:请举出Python中使用多态的例子:len"""
len('hello')
len([1,2,3,4])
len((1,2,3,4))
def len(obj):
return obj.__len__
len('helloworld')
len([1,2,3,4])
len((1,2,3,4))