python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

时间:2022-01-20 11:21:07

先来讲一个例子

老师有生日,怎么组合呢?

class Birthday:  # 生日
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day class Teacher: # 老师
def __init__(self,name,birth):
self.name = name
self.birthday = birth alex = Teacher('alex','2018-7-14')
print(alex.birthday)

执行输出:

2018-7-14

但是这么传日期不好,需要分开,使用组合方式。

class Birthday:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day class Teacher:
def __init__(self,name,birth):
self.name = name
self.birthday = birth birth = Birthday(2018,7,14)
alex = Teacher('alex',birth)
print(birth.year)
print(alex.birthday.year) # 调用组合对象中的属性

执行输出:

2018
2018

定义一个方法,查看完整的生日

class Birthday:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day def fmt(self):
return '%s-%s-%s'%(self.year,self.month,self.day) class Teacher:
def __init__(self,name,birth):
self.name = name
self.birthday = birth birth = Birthday(2018,7,14)
alex = Teacher('alex',birth)
print(birth.year)
print(alex.birthday.year) # 调用组合对象中的属性
print(alex.birthday.fmt()) # 调用组合对象中的方法,要加括号

执行输出:

2018
2018
2018-7-14

Teacher也可以定义一个方法,执行Birthday类里面的方法

class Birthday:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day def fmt(self):
return '%s-%s-%s'%(self.year,self.month,self.day) class Teacher:
def __init__(self,name,birth):
self.name = name
self.birthday = birth def birth_month(self):
return self.birthday.fmt() # 引用组合对象的方法 birth = Birthday(2018,7,14)
alex = Teacher('alex',birth)
print(birth.year)
print(alex.birthday.year) # 调用组合对象中的属性
print(alex.birthday.fmt()) # 调用组合对象中的方法,要加括号
print(alex.birth_month())

执行输出:

2018
2018
2018-7-14
2018-7-14

组合就是把一个对象,作为另外一个类的属性

讲一个继承的例子:


属性 性别 品种
方法 吃 喝 爬树


属性 性别 品种
方法 吃 喝 看门

从上面可以看出,狗和猫有共同的属性和方法,唯独有一个方法是不一样的。
那么是否可以继承呢?

class Animal:  # 动物
def __init__(self,name,sex,kind):
self.name = name
self.sex = sex
self.kind = kind
def eat(self): # 吃
print('%s is eating'%self.name) def drink(self): # 喝
print('%s is drinking'%self.name) class Cat(Animal): # 猫
def climb(self): # 爬树
print('%s is climbing'%self.name) class Dog(Animal): # 狗
def watch_door(self): # 看门
print('%s is watching door'%self.name) tom = Cat('tom','公','招财猫') # 实例化对象
hake = Dog('hake','公','藏獒')
print(Cat.__dict__) # Cat.__dict__ Cat类的命名空间中的所有名字
print(tom.__dict__) # tom.__dict__ 对象的命名空间中的所有名字
tom.eat() # 先找自己对象的内存空间 再找类的空间 再找父类的空间
tom.climb() # 先找自己的内存空间 再找类的空间 

执行输出:

{'__doc__': None, 'climb': <function Cat.climb at 0x000001C95178AAE8>, '__module__': '__main__'}
{'sex': '公', 'name': 'tom', 'kind': '招财猫'}
tom is eating
tom is climbing

实例化猫,需要4个步骤

1.确认自己没有init方法
2.看看有没有父类
3.发现父类Animal有init
4.看着父类的init方法来传参数

__dict__只有对象的命名中的所有名字

一、object类

class A:pass
A()

实例化的过程

  1.创建一个空对象
  2.调用init方法 
  3.将初始化之后的对象返回调用处

那么问题来了,A调用了init方法了吗?答案是 调用了

why?它明明没有啊?

所有的类都继承了object类
查看object的源码,可以找到__init__方法

    def __init__(self): # known special case of object.__init__
""" Initialize self. See help(type(self)) for accurate signature. """
pass

既然A继承了object类,那么它肯定执行了父类object的__init__方法

加一段注释

class A:
'''
这是一个类
'''
pass a = A()
print(A.__dict__) # 双下方法 魔术方法

执行输出:

{'__doc__': '\n 这是一个类\n ', '__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>}

可以看到__doc__方法获取注释信息

object,带双下划线的方法,有2个名字,比如 双下方法,魔术方法
任何类实例化都经历3步。如果类没有init,由object完成了。

二、继承与派生

比如人工大战,人类和狗有共同属性,比如名字,血量,攻击了。还有共同的方法吃药

class Animal:
def __init__(self,name,hp,ad):
self.name = name # 名字
self.hp = hp # 血量
self.ad = ad # 攻击力 def eat(self):
print('%s吃药回血了' % self.name) class Person(Animal):
def attack(self,dog): # 派生类
print('%s攻击了%s' %(self.name,dog.name)) class Dog(Animal):
def bite(self,person): # 派生类
print('%s咬了%s' %(self.name,person.name)) alex = Person('alex',100,10)
print(alex.__dict__)

执行输出:

{'ad': 10, 'hp': 100, 'name': 'alex'}

但是还有不同的,比如人类有性别,狗类有品种

怎么办呢?可以在子类init里面加属性

Person增加init方法

class Animal:
def __init__(self,name,hp,ad):
self.name = name # 名字
self.hp = hp # 血量
self.ad = ad # 攻击力 def eat(self):
print('%s吃药回血了' % self.name) class Person(Animal):
def __init__(self,sex):
self.sex = sex
def attack(self,dog): # 派生类
print('%s攻击了%s' %(self.name,dog.name)) class Dog(Animal):
def __init__(self,kind):
self.kind = kind
def bite(self,person): # 派生类
print('%s咬了%s' %(self.name,person.name)) # 人 sex
alex = Person('alex') # 此时只能传一个参数,否则报错
print(alex.__dict__)

执行输出:

{'sex': 'alex'}

发现和上面的例子少了一些属性,animal继承的属性都没有了。what?
因为子类自己有init方法了,它不会执行父类的init方法
那么如何执行父类的init呢?同时保证自己的init方法也能执行?

class Animal:
def __init__(self,name,hp,ad):
self.name = name # 名字
self.hp = hp # 血量
self.ad = ad # 攻击力 def eat(self):
print('%s吃药回血了' % self.name) class Person(Animal):
def __init__(self,name,hp,ad,sex):
Animal.__init__(self,name,hp,ad) # 执行父类方法
self.sex = sex
def attack(self,dog): # 派生类
print('%s攻击了%s' %(self.name,dog.name)) class Dog(Animal):
def __init__(self,name,hp,ad,kind):
Animal.__init__(self, name, hp, ad)
self.kind = kind
def bite(self,person): # 派生类
print('%s咬了%s' %(self.name,person.name)) # 人 sex
alex = Person('alex',100,10,'female') # 实例化
print(alex.__dict__)

执行输出:

{'ad': 10, 'name': 'alex', 'hp': 100, 'sex': 'female'}

三、super方法

Animal.__init__(self, name, hp, ad) 是直接使用类名.方法名 这样执行的。

第二种写法,使用super

class Animal:
def __init__(self,name,hp,ad):
self.name = name # 名字
self.hp = hp # 血量
self.ad = ad # 攻击力 def eat(self): # 吃药
print('%s吃药回血了' % self.name)
self.hp += 20 class Person(Animal):
def __init__(self,name,hp,ad,sex):
#Animal.__init__(self,name,hp,ad) # 执行父类方法
# super(Person,self).__init__(name,hp,ad) # 完整写法.在单继承中,super负责找到当前类所在的父类,在这个时候不需要再手动传self
super().__init__(name, hp, ad) # 简写.效果同上。它不需要传参数Person,self。因为它本来就在类里面,自动获取参数
self.sex = sex
def attack(self,dog): # 派生类
print('%s攻击了%s' %(self.name,dog.name)) class Dog(Animal):
def __init__(self,name,hp,ad,kind):
super().__init__(name, hp, ad)
self.kind = kind
def bite(self,person): # 派生类
print('%s咬了%s' %(self.name,person.name)) # 人 sex
alex = Person('alex',100,10,'female') # 实例化
print(alex.__dict__)

执行输出:

{'ad': 10, 'name': 'alex', 'hp': 100, 'sex': 'female'}

类外层调用eat方法

#父类有eat,子类没有
alex.eat() #找父类

执行输出:

alex吃药回血了

比如人吃药要扣钱,狗吃药,不要钱。

在Animal类中,eat方法,执行时,没有扣钱。

那么就需要在人类中添加eat方法,定义扣钱动作

class Animal:
def __init__(self,name,hp,ad):
self.name = name # 名字
self.hp = hp # 血量
self.ad = ad # 攻击力 def eat(self):
print('%s吃药回血了' % self.name) class Person(Animal):
def __init__(self,name,hp,ad,sex):
#Animal.__init__(self,name,hp,ad) # 执行父类方法
# super(Person,self).__init__(name,hp,ad) # 完整写法.在单继承中,super负责找到当前类所在的父类,在这个时候不需要再手动传self
super().__init__(name, hp, ad) # 简写.效果同上。它不需要传参数Person,self。因为它本来就在类里面,自动获取参数
self.sex = sex # 性别
self.money = 0 # 增加默认属性money
def attack(self,dog): # 派生类
print('%s攻击了%s' %(self.name,dog.name))
def eat(self): # 重新定义eat方法
super().eat() # 执行父类方法eat
print('eating in Person')
self.money -= 50 # 扣钱
class Dog(Animal):
def __init__(self,name,hp,ad,kind):
super().__init__(name, hp, ad)
self.kind = kind
def bite(self,person): # 派生类
print('%s咬了%s' %(self.name,person.name)) # 人 sex
alex = Person('alex',100,10,'female') # 实例化
alex.eat() # 子类有eat 不管父类中有没有,都执行子类的

执行输出:

alex吃药回血了
eating in Person

父类方法,如果子类有个性化需求,可以重新定义次方法

在类外面

当子类中有,但是想要调父类的

alex = Person('alex',100,10,'female')   # 实例化
Animal.eat(alex) # 指名道姓
super(Person,alex).eat() # 效果同上,super(子类名,子类对象)方法,一般不用

执行输出:

alex吃药回血了
alex吃药回血了

一般不会在类外面,执行super方法。都是在类里面调用父类方法

 super是帮助寻找父类的

在外部,super没有简写

四、钻石继承

父类是新式类,那么子类全是新式类
在python3里面没有经典类

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

这个形状,像一个钻石

老外喜欢浪漫,有些书籍写的叫钻石继承

代码如下:

class A:
def func(self):
print('A')
class B(A):
def func(self):
print('B')
class C(A):
def func(self):
print('C')
class D(B,C):
def func(self):
print('D') d = D()
d.func()

执行输出:D

把D的代码注释

class D(B,C):
pass
# def func(self):
# print('D')

执行输出:B

把B的代码注释

class B(A):
pass
# def func(self):
# print('B')

执行输出:C

把C的代码注释

class C(A):
pass
# def func(self):
# print('C')

执行输出:A

看图,查看顺序

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

在看一个龟壳模型

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

代码如下:

class A:
def func(self):
print('A')
class B(A):
pass
def func(self):
print('B')
class C(A):
pass
def func(self):
print('C')
class D(B):
pass
def func(self):
print('D')
class E(C):
pass
def func(self):
print('E')
class F(D,E):
pass
def func(self):
print('F') f = F()
f.func()

执行输出: F

看图,查看顺序

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

在这个例子中,A为顶点,因为有2个类继承了A

在执行第3步时,由于B继承了A,B并没有直接去找A。而是在这这一层中断查找。

由同层的E去查找,然后到C,最后到A

这就是广度优先算法

广度优先搜索算法(英语:Breadth-First-Search,缩写为BFS),又译作宽度优先搜索,或横向优先搜索,是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。

宽度优先搜索,请参考链接

https://baike.baidu.com/item/%E5%AE%BD%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2/5224802?fr=aladdin&fromid=542084&fromtitle=BFS

广度优先算法有点复杂,Python直接提供了方法mro,可以查看搜索循环

f = F()
f.func()
print(F.mro())

执行输出:

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

新式类 多继承 寻找名字的顺序 遵循广度优先

经典面试题

class A:
def func(self):
print('A')
class B(A):
def func(self):
super().func()
print('B')
class C(A):
def func(self):
super().func()
print('C')
class D(B,C):
def func(self):
super().func()
print('D') d = D()
d.func()

执行输出:

A
C
B
D

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

super():
    在单继承中就是单纯的寻找父类
    在多继承中就是根据子节点 所在图的mro循环找寻下一个类

在上的例子中,super不是找父类的,它是找下一个节点的

遇到多继承和super

    对象.方法

        找到这个对象对应的类

        将这个类的所有父类都找到画成一个图

        根据图写出广度优先的顺序

        再看代码,看代码的时候,要根据广度优先顺序图来找对应的super

深度优先

深度优先是“一路摸到黑”,也就是说深度优先搜索会不假思索地一直扩展一个状态直到到达不能被扩展的叶子状态。

要用Python2测试,代码如下:

class A:
def func(self):
print('A')
class B(A):
def func(self):
print('B')
class C(A):
def func(self):
print('C')
class D(B,C):
def func(self):
print('D') d = D()
d.func()

使用cmd执行

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

在python2里面,不手动继承object,比如class A(object)

就是经典类,比如class A

在这个例子中B继承了A,B再去找A,执行输出A

所有的路线,不走重复的

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

深度优先,一条路走到黑
找不到,就会回来找其他的.

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)

总结
经典类 :在python2.*版本才存在,且必须不继承object

    遍历的时候遵循深度优先算法
    没有mro方法
    没有super()方法

新式类 :在python2.X的版本中,需要继承object才是新式类
    遍历的时候遵循广度优先算法
    在新式类中,有mro方法
    有super方法,但是在2.X版本的解释器中,必须传参数(子类名,子类对象)

今日内容总结:

python 全栈开发,Day20(object类,继承与派生,super方法,钻石继承)