面向对象之继承
目录
面向对象之继承
初步了解继承
什么是继承?
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(Python支持多继承),父类又可以称为基类或者超类,新建的类称为派生类或子类。
子类会“遗传”父类的属性,从而解决代码重用问题。
# 父类
class ParentClass1:
desc = "这是一个基类"
def show_info(self):
print(self.desc)
def make_money(self):
print("一天赚一个亿...")
class ParentClass2:
pass
# 子类
class SubClass1(ParentClass1):
pass
# 继承多个父类
class SubClass2(ParentClass1, ParentClass2):
pass
s1=SubClass1()
s2=SubClass2()
s1.show_info()
s1.make_money()
s2.show_info()
s2.make_money()
print('='*30)
print(SubClass1.__bases__) # 获取当前类的父类
print(SubClass1.__bases__[0].__name__) # 获取当前类的父类是元组的形式,索引取值后再用用__name__获取类名程
print('='*30)
print(SubClass2.__bases__)
print(SubClass2.__bases__[0].__name__)
print(SubClass2.__bases__[1].__name__)
'''
这是一个基类
一天赚一个亿...
这是一个基类
一天赚一个亿...
==============================
(<class '__main__.ParentClass1'>,)
ParentClass1
==============================
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
ParentClass1
ParentClass2
'''
以上可以了解到子类通过继承父类,就可以使用父类中的属性(特征和技能)
2.经典类和新式类
经典类和新式类的区分
'''
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
4.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现
'''
ParentClass1.__bases__
# (<class 'object'>,)
ParentClass2.__bases__
#(<class 'object'>,)
1.新式类的继承问题
# 在python3:
# 新式类:
class Foo(object): # object 即使不写也是默认继承object类
pass
class Goo(Foo):
pass
print(Foo.__bases__) # (<class 'object'>,)
print(Goo.__bases__) # (<class '__main__.Foo'>,)
print(object) # <class 'object'>
2. 经典类的继承问题
# coding:utf-8 #在pycharm中使用Python2解释器时,一定要加文件头改变Python2的编码方式(ASCII)
# 在Python2中如果一个类显示地继承了object类,就相当Python3中的新式类,就是新式类。
# Python2中的经典类:
class Foo: # Python2中没有显示继承object类就是经典类
pass
class Goo(Foo):
pass
print(Foo.__bases__)
print(Goo.__bases__)
'''
()
(<class __main__.Foo at 0x0201FA78>,)
'''
3.元类type和object的区别
print(object.__bases__)
print(type.__bases__)
print(isinstance(object,type)) # True
print(isinstance(type,object)) # True
'''
()
(<class 'object'>,)
True
True
3.继承与抽象
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
例如
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
#猫和狗有大量相同的内容
class 猫:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
==========================第二部分
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
伪代码如下:
class 动物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
def 喵喵叫(self):
print '喵喵叫'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
def 汪汪叫(self):
print '喵喵叫'
==========================第三部分
#继承的代码实现
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(self):
print ("%s 撒 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def cry(self):
print('喵喵叫')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def cry(self):
print('汪汪叫')
# ######### 执行 #########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
4.继承背景下对象属性查找顺序
在继承背景下,对象属性的查找顺序:
1.对象查找属性会先从对象的名称空间中查找.
2.若对象没有,则会去类里面找.
3.若当前类是子类,并且没有对象找的属性,会去父类中查找
注意: 对象查找属性,若子类有,不管父类有没有,以子类的为准.
# 例1
class People:
school = '社会大学'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 子类
class Teacher(People):
# 老师修改分数技能
def change_score(self):
print(f'老师[{self.name} 修改分数...]')
tea1 = Teacher('zhang', 17, 'male')
print(tea1.school)
#查看对象名称空间
print(tea1.__dict__)
#__class__: 对象的属性,查看当前对象的类.
#查看子类名称空间
print(tea1.__class__.__dict__)
#查看父类名称空间
print(tea1.__class__.__bases__[0].__dict__)
'''
社会大学
{'name': 'zhang', 'age': 17, 'sex': 'male'}
{'__module__': '__main__', 'change_score': <function Teacher.change_score at 0x0000000009FF4F28>, '__doc__': None}
{'__module__': '__main__', 'school': '社会大学', '__init__': <function People.__init__ at 0x0000000009FF4EA0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
'''
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
'''
# f2的查找顺序:对象->类Bar->父类->Foo->object
# self.f1(),由于f1()属于父类Foo,所以从父类里面找f1,假如父类没有f1,也不会去子类里面找,而是去object 找。
5.派生
派生:派生指的是子类继承父类的属性,并且派生出新的属性.(*****)
子类派生出新的属性,若与父类的属性相同,则以子类的为准。继承是谁与谁的关系, 指的是类与类的关系,子类与父类是从属关系。
注意:当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
6.子类派生出新的属性,并重用父类的属性
解决方式有两种:
方式一:
直接通过 父类.(调用)__init__或者父类名(调用).函数名(),把__init__或函数当做普通函数使用,传入对象与继承的属性(该传几个值就传几个).
方式二:
super是一个特殊的类,在子类中调用super()会得到一个特殊的对象,
通过"."指向的是父类的名称空间.(这里我们可以这样理解帮助记忆:super可以看成是超类的意思,那么super加括号就是一个对象,对象在调用自己类中方法的时候就是绑定方法,会自动将自己当做第一个参数传入,因此,子类重用父类中的属性的时候通过super().__init__()或者super.函数名()时,就不需要传self了。)
# 例1
class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class Student(People):
def __init__(self,course,name,age,sex):
print(self)
People.__init__(self,name,age,sex)
# super().__init__(name,age,sex)
print(self) # 与上面的一个self是同一个self
self.course=course
def choosee_course(self):
print(f'学生{self.name}正在选课{self.course}。。。。')
class Teacher(People):
def __init__(self,name,age,sex,level,val):
People.__init__(self,name,age,sex)
# super().__init__(name, age, sex)
self.level=level
self.val=val
def teach_course(self):
print(f'老师{self.name}正在上课中。。。')
stu=Student('python','zhang',18,'male')
tea=Teacher('wang',17,'male',9,180000)
stu.choosee_course()
tea.teach_course()
print(Teacher.mro())
print(Student.mro())
print(People.mro()) # Python3中默认继承object类,而且是新式类。
'''
<__main__.Student object at 0x0000000009FBD4E0>
<__main__.Student object at 0x0000000009FBD4E0>
学生zhang正在选课python。。。。
老师wang正在上课中。。。
[<class '__main__.Teacher'>, <class '__main__.People'>, <class 'object'>]
[<class '__main__.Student'>, <class '__main__.People'>, <class 'object'>]
[<class '__main__.People'>, <class 'object'>]
'''
# 例2
class Person:
def __init__(self,name,gender,age,*args):
self.name = name
self.gender = gender
self.age = age
self.aa()
def aa(self):
print("aa run")
def say_hi(self):
print("name:%s ,gender:%s,age:%s" % (self.name,self.gender,self.age))
class Student(Person):
def __init__(self,name,gender,age,number):
#Person.__init__(self,name,gender,age)
super().__init__(name,gender,age)
self.number= number
def say_hi(self):
Person.say_hi(self)
#sper().say_hi()
print("numnber:%s" % self.number)
stu = Student("rose","mael",20,"old01")
stu.say_hi()
7.类的继承顺序
1.继承原理(mro()线型顺序列表)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
2.非菱形结构
#如果继承关系为非菱形结构,则会一条分支一条分支的顺序查找,直到找到我们想要的属性。
class H:
pass
class G:
pass
class E:
pass
class F:
pass
class D(H):
pass
class C(G):
pass
class B(E,F):
pass
class A(B,C,D):
pass
# 新式类继承顺序:A->B->E->F->C->G->D->H->object
print(A.mro()) # python2中的经典类中没有mro()列表(所有基类的线性顺序列表)
'''
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.H'>, <class 'object'>]
'''
3.菱形继承
菱形继承:当一个类有多个父类,且多个父类有共同的基类,这就叫做菱形继承。
新式类 : 多继承的情况下,在要查找属性不存在时,会按照广度优先查找,从左往右一个分支一个分支的查找,在最后一个分支才去查找顶级类;
经典类 : 多继承的情况下,在要查找属性不存在时,会按照深度优先查找,从左往右一个分支一个分支的查找,在第一个分支就查找顶级类.
1). 新式类菱形属性查找(广度优先)
# 新式类菱形继承属性查找(广度优先)
class G(object):
x=777
pass
class F(G):
x=666
pass
class E(G):
x=555
pass
class D(G):
x=444
pass
class C(F):
x=333
pass
class B(E):
x=222
pass
class A(B,C,D):
x=111
pass
a=A()
a.x=888
print(a.x)
print(A.mro())
print(a.__class__)
'''
888
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
<class '__main__.A'>
'''
2).经典类菱形继承属性查找(深度优先)
# 经典类菱形属性查找(深度优先):只在Python中才有经典类,因此在Python2中演示要加文件头
#coding:utf8
class G: # 没有显示继承object类
# x=777
pass
class F(G):
# x=666
pass
class E(G):
# x=555
pass
class D(G):
x=444
pass
class C(F):
# x=333
pass
class B(E):
# x=222
pass
class A(B,C,D):
# x=111
pass
a=A()
# a.x=888
print(a.x)
print(a.__class__)
# x的查找顺序:a(x)->A(x)->B(x)->E(x)->G(x)->C(x)->F(x)->D(x)
'''
444
__main__.A
'''
8.通过继承实现修改json模块数据类型
# 开源者的角度: 修改json源码
class MyJson(json.JSONEncoder):
def default(self, o):
# 子类派生的功能
# 判断o是否式datetime的一个实例
if isinstance(o, datetime):
return o.strftime('%Y-%m-%d %X')
elif isinstance(o, date):
return o.strftime('%Y-%m-%d')
else:
# 继承父类的default方法的功能
return super().default(self, o)
dict1 = {
'name': 'zhang',
'today': datetime.today(),
'today2': date.today()
}
res = json.dumps(dict1, cls=MyJson) # cls=None,默认指向的是原json的JSONEncoder
print(res)
'''
{"name": "zhang", "today": "2019-10-10 20:02:59", "today2": "2019-10-10"}
'''