先来讲一个例子
老师有生日,怎么组合呢?
class Birthday: # 生日
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
class Teacher: # 老师<br>
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’:
{‘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‘:
可以看到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)<br> 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 里面没有经典类


这个形状,像一个钻石
老外喜欢浪漫,有些书籍写的叫钻石继承
代码如下:
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
看图,查看顺序

在看一个龟壳模型

代码如下:
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
看图,查看顺序

在这个例子中,A 为顶点,因为有 2 个类继承了 A
在执行第 3 步时,由于 B 继承了 A,B 并没有直接去找 A。而是在这这一层中断查找。
由同层的 E 去查找,然后到 C,最后到 A
这就是广度优先算法
广度优先搜索算法(英语:Breadth-First-Search,缩写为 BFS),又译作宽度优先搜索,或横向优先搜索,是一种图形搜索算法。简单的说,BFS 是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。
宽度优先搜索,请参考链接
广度优先算法有点复杂,Python 直接提供了方法 mro,可以查看搜索循环
f = F()
f.func()
print(F.mro())
执行输出:
F
[
新式类 多继承 寻找名字的顺序 遵循广度优先
经典面试题
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

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 执行

在 python2 里面,不手动继承 object,比如 class A(object)
就是经典类,比如 class A
在这个例子中 B 继承了 A,B 再去找 A,执行输出 A
所有的路线,不走重复的

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

总结
经典类 :在 python2.*版本才存在,且必须不继承 object
遍历的时候遵循深度优先算法
没有 mro 方法
没有 super()方法
新式类 :在 python2.X 的版本中,需要继承 object 才是新式类
遍历的时候遵循广度优先算法
在新式类中,有 mro 方法
有 super 方法,但是在 2.X 版本的解释器中,必须传参数(子类名,子类对象)
今日内容总结:
