先讲一个例子:
#老师有生日,怎么组合哪? class Birthday: # 生日def __init__(self,year,month,day):self.year = yearself.month = monthself.day = dayclass Teacher: # 老师<br>def __init__(self,name,birth):self.name = nameself.birthday = birthalex = Teacher('alex','2018-7-14') print(alex.birthday)# 2018-7-14#但是这么传日期不好,需要分开,使用组合方式: class Birthday:def __init__(self,year,month,day):self.year = yearself.month = monthself.day = dayclass Teacher:def __init__(self,name,birth):self.name = nameself.birthday = birthbirth = Birthday(2018,7,14) alex = Teacher('alex',birth) # 用实例建立桥梁 print(birth.year) print(alex.birthday.year) # 调用组合对象中的属性# 输出结果 2018 2018 2018-7-14
Teacher 也可以定义一个方法,执行Birthday类里面的方法:
class Birthday:def __init__(self,year,month,day):self.year = yearself.month = monthself.day = daydef fmt(self):return '%s-%s-%s'%(self.year,self.month,self.day)class Teacher:def __init__(self,name,birth):self.name = nameself.birthday = birthdef 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 = nameself.sex = sexself.kind = kinddef 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
一、Object类
class A:pass A()
#实例化的过程
1.创建一个空对象
2.调用init方法 其实A是继承的object,而其初识函数是空的
3.将初始化之后的对象返回调用处
以上代码,A调用了init方法了?答案是:调用了! 这是什么调用,调用了object类的初始化函数
所有的类都继承了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:'''这是一个类'''passa = A() print(A.__dict__) # 双下方法 魔术方法 #运行结果:{'__doc__': '\n 这是一个类\n ', '__module__': '__main__', '__dict__':
<attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__'
of 'A' objects>}
可以在上面的结果中找到两个带object的方法,很明显这就是调用了object类!
总结:
任何类实例化都经历3步,如果类没有init,由object完成了。
class Parent:passclass Son(Parent):passprint(Son.__bases__) # (<class '__main__.Parent'>,)class Parent1:pass class Parent2:passclass Son(Parent1,Parent2):passprint(Son.__bases__) # (<class '__main__.Parent1'>, <class'__main__.Parent2'>)
二、单继承:
比如人工大战,人类和狗有共同属性,比如名字,血量,攻击了。还有共同的方法吃药:
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 = sexdef attack(self,dog): # 派生类print('%s攻击了%s' %(self.name,dog.name))class Dog(Animal):def __init__(self,kind):self.kind = kinddef 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) # 执行父类方法 也可以使用super().__init__(name,hp,ad),它不需要self参数,
super(Person,self).__init__(name,hp,ad)self.sex = sexdef 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 = kinddef 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'}
四、多继承
多继承和单继承是一样的
如果对象使用名字是子类中有的,那么一定用子类的
子类没有,可以到多个父类中去寻找,问题来了,继承谁的那?
class FlyAnimal:def fly(self):pass class SwimAnimal:def swim(self):passdef eat():pass class WalkAnimal:def walk(self):passdef eat():passclass Frog(SwimAnimal,WalkAnimal): pass class Tiger(SwimAnimal,WalkAnimal):pass class Swan(FlyAnimal,SwimAnimal,WalkAnimal):pass class Parrot(FlyAnimal,WalkAnimal):def talk(self):pass
三、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 += 20class Person(Animal):def __init__(self,name,hp,ad,sex):#Animal.__init__(self,name,hp,ad) # 执行父类方法 # super(Person,self).__init__(name,hp,ad) # 完整写法.在单继承中,super负责找到
当前类所在的父类,在这个时候不需要再手动传selfsuper().__init__(name, hp, ad) # 简写.效果同上。它不需要传参数Person,self。
因为它本来就在类里面,自动获取参数self.sex = sexdef 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 = kinddef 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负责找到当前类所在的父类,在这个时候不需要再手动传selfsuper().__init__(name, hp, ad) # 简写.效果同上。它不需要传参数Person,self。因为它本来就在类里面,自动获取参数self.sex = sex # 性别self.money = 0 # 增加默认属性moneydef attack(self,dog): # 派生类print('%s攻击了%s' %(self.name,dog.name))def eat(self): # 重新定义eat方法super().eat() # 执行父类方法eatprint('eating in Person')self.money -= 50 # 扣钱 class Dog(Animal):def __init__(self,name,hp,ad,kind):super().__init__(name, hp, ad)self.kind = kinddef bite(self,person): # 派生类print('%s咬了%s' %(self.name,person.name))# 人 sex alex = Person('alex',100,10,'female') # 实例化 alex.eat() # 子类有eat 不管父类中有没有,都执行子类的#执行结果: # 人 sex alex = Person('alex',100,10,'female') # 实例化 alex.eat() # 子类有eat 不管父类中有没有,都执行子类的
# 总结:父类方法,如果子类有个性化需求,可以重新定义次方法。
在类外面
当子类中有,但是想要调父类的。
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
广度优化 :
1.如果从左到右第一个类, 2.在后面的继承顺序中也是第一个,或者不再出现在后面的继承顺序中 3.那么就可以把这个点提取出来,作为继承顺序中的第一个类
广度优先搜索算法(英语:Breadth-First-Search,缩写为BFS),又译作宽度优先搜索,或横向优先搜索,是一种图形搜索算法。简单的说,
BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。
也就是第一次不走到头,头可能被多个对象继承,如A,从最后一个引用的类进入A
看图,查看顺序:
再看一个龟壳模型:
代码如下:
class A:def func(self):print('A') class B(A):passdef func(self):print('B') class C(A):passdef func(self):print('C') class D(B):passdef func(self):print('D') class E(C):passdef func(self):print('E') class F(D,E):passdef func(self):print('F')f = F() f.func()
#执行输出: F
看图,查看顺序:
# 在python2中还有深度优先,就是第一次会取到最深处,F->D->B->A->E->C。
在这个例子中,A为顶点,因为有2个类继承了A
在执行第3步时,由于B继承了A,B并没有直接去找A。而是在这这一层中断查找。
由同层的E去查找,然后到C,最后到A,这就是广度优先算法
注意:在广度优先算法中,Python直接提供了方法mro,可以查看搜索循环:
f = F() f.func() print(F.mro()) #需要注意的是super始终是遵循mro的#执行输出:#通过mro可以查看顺序 F [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
#总结: 新式类 多继承 寻找名字的顺序,遵循广度优先(mro)!
经典面试题:
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()#执行结果: 在D(B,C)中先看B中,再看C,从C到A A C B D
在上的例子中,super不是找父类的,它是找下一个节点的
遇到多继承和super
对象.方法
找到这个对象对应的类
将这个类的所有父类都找到画成一个图
根据图写出广度优先的顺序
再看代码,看代码的时候,要根据广度优先顺序图来找对应的super
总结:super():
1、在单继承中就是单纯得寻找父类
2、在多继承中就是根据子节点 通过mro循环寻找下一个类
深度优化 :只在Python2中,且没有继承object,没有mro和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()#执行结果: D 在Python2里面,不手动继承Object,比如class A(object)就是经典类,比如calss A,在例子中B继承了A,B在去找A,执行输出A
顺序如图:
深度优化,一条路走到黑,找不到,就回来找其他的
总结:
经典类:在Python2.*版本才存在,且必须不继承object
1、遍历的时候遵循深度优化算法
2、没有mro() 和 super()方法
新式类: 在Python2.*版本中,需要继承object才是新式类
1、遍历的时候遵循广度优先算法
2、在新式类中,有mro方法,同时也有super方法,但是在2.*版本中,必须传参数(子类名,子类对象)