今天整理类的组合以及类的三大特性
1.类的组合
2.类的继承
3.类的封装
4.类的多态
开始今日份整理
1.类的组合
类与类之间,并不是独立的,很多的时候在正常使用的时候都是类与类之间互相调用,所以就需要对类与类之间的关联或者是联系进行整理一下,就拿之前的英雄联盟的游戏人物之间的关系举例。
1.1类的组合
在定义lol中不同的英雄人物,那么正常游戏过程中,是需要使用武器等,这个时候就用到了组合,查看代码
class Game_role(): def __init__(self,name,ad,hp): self.name = name self.ad = ad self.hp = hp def attack(self,obj): obj.hp -= self.ad print('{}打了{}一次,{}丢失{}点血量,现在血量为{}'.format(self.name,obj.name,obj.name,self.ad,obj.hp)) def equip_weapon(self,obj): self.weapon = obj class Weapon(): def __init__(self,name,ad): self.name = name self.ad = ad def attack_weapon(self,obj1,obj2): obj2.hp -=(self.ad+obj1.ad) print('{}用{}打了{}一下,{}还剩{}点血量'.format(obj1.name,self.name,obj2.name,obj2.name,obj2.hp)) r1 = Game_role('盖伦',15,200) r2 = Game_role('剑姬',30,140) w1 = Weapon('三相',20) r1.equip_weapon(w1) r1.weapon.attack_weapon(r1,r2) #结果 盖伦用三相打了剑姬一下,剑姬还剩105点血量
这个算最基本的类之间的组合,对于类的组合,其实就是给一个对象封装一个属性,而这个属性是另外一个对象,也是最low的了。不过类的组合也是有好处的
- 代码之间的关系更加合理
- 类与类之间的耦合性增强
一些小的测试题
1,暴力摩托程序(完成下列需求): 1.1创建三个游戏人物,分别是: • 苍井井,女,18,攻击力ad为20,血量200 • 东尼木木,男,20,攻击力ad为30,血量150 • 波多多,女,19,攻击力ad为50,血量80 1.2创建三个游戏武器,分别是: • 平底锅,ad为20 • 斧子,ad为50 • 双节棍,ad为65 1.3 创建三个游戏摩托车,分别是: • 小踏板,速度60迈 • 雅马哈,速度80迈 • 宝马,速度120迈。 完成下列需求(利用武器打人掉的血量为武器的ad + 人的ad): (1)苍井井骑着小踏板开着60迈的车行驶在赛道上。 (2)东尼木木骑着宝马开着120迈的车行驶在赛道上。 (3)波多多骑着雅马哈开着80迈的车行驶在赛道上。 (4)苍井井赤手空拳打了波多多20滴血,波多多还剩xx血。 (5)东尼木木赤手空拳打了波多多30滴血,波多多还剩xx血。 (6)波多多利用平底锅打了苍井井一平底锅,苍井井还剩xx血。 (7)波多多利用斧子打了东尼木木一斧子,东尼木木还剩xx血。 (8)苍井井骑着宝马打了骑着小踏板的东尼木木一双节棍,东尼木木哭了,还剩xx血。(选做) (9)波多多骑着小踏板打了骑着雅马哈的东尼木木一斧子,东尼木木哭了,还剩xx血。
1.2类的依赖
类的依赖:给一个类的方法传了一个参数,而这个参数是一个类的对象,这种依赖关系是关系中紧密型最低的,耦合性最低的,就像上面的lol中人自身的攻击手段,他调用的是对方的对象,这个时候盖伦中有剑姬,而剑姬没有盖伦,并不是像武器,武器已经完全变成盖伦的属性而存在,紧密结合。用大象关冰箱来做一个测试。
class Elephant(): def __init__(self,name): self.name = name def open_door(self,obj): print('1,2,3,开门') obj.open_() def close_door(self,obj): print('3,2,1,关门 ') obj.close_() class Refrigerator(): def __init__(self,name): self.name = name def open_(self): print('门正在打开') def close_(self): print('门正在关闭') e1 = Elephant('神奇的大象') r1 = Refrigerator('海尔') e1.open_door(r1) e1.close_door(r1)
和上面的相比,这次对象同样调用了另外的obj,可是只是使用了方法,并没有让obj成为自己的属性
1.3类的关联,聚合关系,组合关系
其实这个关系还是组合,还是举俩个例子吧,一个陪女友吃饭,一个是教师关联学校
陪女友吃饭
class Boy(): def __init__(self,name,girelfriend=None): self.name = name self.girlfrind = girelfriend def have_dinner(self): if self.girlfrind == None: print('单身狗,吃啥吃!') else: print('%s 与%s共进晚餐'%(self.name,self.girlfrind.name)) def get_girlfrind(self,obj): self.girlfrind = obj def lose_girlfriend(self): self.girlfrind = None class Girl(): def __init__(self,name): self.name = name b1 = Boy('屌丝')#无女友 g1 = Girl('美女') b2 = Boy('小哥',g1)#有娃娃亲 b1.have_dinner() b2.have_dinner()
教师关联学校
class School(): def __init__(self,city,address): self.city = city self.address = address self.teacher_list = [] class Teacher(): def __init__(self,name,language): self.name = name self.language = language self.school = None def binding(self,li): for i,j in enumerate(li): print(i,'学校所在地址为%s'%j.city) choice = int(input('你所选择的学校的序号>>>').strip()) self.school = li[choice]#教师绑定了学校 li[choice].teacher_list.append(self)#学校绑定了教师 print('学校绑定教师成功') school1 =School('北京','昌平') school2 =School('上海','张江') li = [school1,school2] test1 =Teacher('alex','python') test1.binding(li) for i in school1.teacher_list: print(i.name,i.language)
2.类的继承
就像人和狗都属于动物,人和狗都有年龄啊什么的共同方法,那么我们就可以写一个公共类,狗和人继承好了
class Animal: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def eat(self): print('在吃东西') class People(Animal): def __init__(self,name,age,sex,skin): #方式一 Animal.__init__(self,name,age,sex) #方式二 super().__init__(name,age,sex) self.skin = skin def eat(self): print('人在吃东西') #对于狗也是一样
2.1类的单继承
类的单继承就是字面意思,一个类只有一个父类用于继承,父类又称之为基类或者是超类,子类又称之为派生类
就像上面的例子,people类继承了animal类,people类可以使用父类的属性以及方法。
查看子类的继承情况为:print(类.__base__)
# 既要执行子类方法,又要执行父类方法 # 方法一: Aniaml.__init__(self,name,sex,age) # p1 = Person('春哥','laddboy',18,'有思想') # print(p1.__dict__) # 方法二:super # p1 = Person('春哥','laddboy',18,'有思想') # print(p1.__dict__) # def func(self): # pass # self = 3 # func(self) # p1 = Person('春哥','laddboy',18,'有思想') # p1.eat()
2.2类的多继承
类的多继承可以这么理解,就是一个儿子有多个爹,然后类的继承就需要排序了。对于类的分类,主要有经典类与新式类俩种
- 经典类:python 2.x系列中,不继承基类object,对于类的查找属于一条道走到黑,深度优先
- 新式类:python 2.x系列中,继承基类object,python3.x系列中,默认都是新式类,查找按照c3算法
经典类的查询,如图,就是一条道走到黑
新式类的查询,采用c3算法
c3算法:# #mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )
下面是推倒过程
# #mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] ) mro(K(H,I)) =[K]+merge(mro(H),mro(I),[H,I]) mro(H(E,F)) = [H] +merge(mro(E),mro(F),[E,F]) mro(E(B,C)) = [E] +merge([B,A],[C,A],[B,C]) mro(E(B,C)) = [E,B] +merge([A],[C,A],[C]) mro(E(B,C)) = [E,B,C,A] mro(F(C,D)) = [F] +merge([C,A],[D,A],[C,D]) mro(F(C,D)) = [F,C] +merge([A],[D,A],[D]) mro(F(C,D)) = [F,C,D,A] mro(H(E,F)) = [H] +merge([E,B,C,A],[F,C,D,A],[E,F]) mro(H(E,F)) = [H,E] +merge([B,C,A],[F,C,D,A],[F]) mro(H(E,F)) = [H,E,B] +merge([C,A],[F,C,D,A],[F]) mro(H(E,F)) = [H,E,B,F] +merge([C,A],[C,D,A]) mro(H(E,F)) = [H,E,B,F,C,] +merge([A],[D,A]) mro(H(E,F)) = [H,E,B,F,C,D,A] mro(I(F,G)) =[I] +merge(mro(F),mro(G),[F,G]) mro(F(C,D)) = [F] +merge(mro[C],mro[D],[C,D]) mro(F(C,D)) = [F] +merge([C,A],D,A,[C,D]) mro(F(C,D)) = [F,C] +merge([A],D,A,[D]) mro(F(C,D)) = [F,C,D,A] mro(G)=[D,A] mro(I(F,G)) =[I] +merge([F,C,D,A],[G,D,A],[F,G]) mro(I(F,G)) =[I,F] +merge([C,D,A],[G,D,A],[G]) mro(I(F,G)) =[I,F,C] +merge([D,A],[G,D,A],[G]) mro(I(F,G)) =[I,F,C,G] +merge([D,A],[D,A]) mro(I(F,G)) =[I,F,C,G,D,A] mro(K(H,I)) =[K]+merge([H,E,B,F,C,D,A],[I,F,C,G,D,A],[H,I]) mro(K(H,I)) =[K,H]+merge([E,B,F,C,D,A],[I,F,C,G,D,A],[I]) mro(K(H,I)) =[K,H,E,B,I]+merge([F,C,D,A],[F,C,G,D,A]) mro(K(H,I)) =[K,H,E,B,I,F,C]+merge([D,A],[G,D,A]) mro(K(H,I)) =[K,H,E,B,I,F,C,G,D,A] #最后的执行顺序就是[K,H,E,B,I,F,C,G,D,A]
如果不用这种手算的推导式,其实可以直接调用方法查看
print(K.__mro__)
#结果和上面是一致的
super()也是按照上面的c3算法来调用的
根据上面做一些派生
子类可以添加自己新的属性或者在自己这里定义这些属性(不会影响父类),需要注意的是,一旦重新定义了与父类相同的名字的属性,会以自己为准。
在子类中,新建的重名函数类型,在编辑函数功能时,有可能调用父类的相同名字的函数功能时,应该用普通函数方法一样,因此即使是self也应该传值进去,例如类名.func(参数)
继承原理:
- 子类会先于父类检查
- 多个父类会根据他们在列表中的顺序被检查
- 如果对下一个类存在俩个合法选择,选择第一个父类
3.类的封装
在讲类的封装的前提需要说一下类方法的隐藏
3.1隐藏
类的结构中可以分静态属性以及动态属性,按照另外一个角度,可以划分为公有,私有属性
class Boss(): name = 'alex' __secretary =['女一','男二','野模']#私有静态属性 def __init__(self,username,password): self.username = username self.__password = password#私有对象属性 def func(self): print('老板在办公') def __func1(self): print('老板在和秘书办公')#私有方法 def print(self): print(self.__secretary) self.__func1() b1 = Boss('alex',123) print(b1.name) #print(b1.__secretary) b1.print()
我们会发现,按照以前的一样的调用方法,根本得不到这些私有属性以及私有方法,如果我们在类内定义一个函数专门用于调用这些私有方法,才能看到这些私有属性,单独的去调用这些私有属性只会报错没有这些方法,我们会发现只有类的内部才能调用,类的外部以及派生类根本无法调用属性。
不过并不是完全无法调用,当我们看类的__dict__方法时,我们会发现,python对类内部这些私有方法属性进行了包装
{'_Boss__secretary': ['女一', '男二', '野模'], '__doc__': None, '__init__': <function Boss.__init__ at 0x016246A8>, 'name': 'alex', '_Boss__func1': <function Boss.__func1 at 0x016D40C0>, 'print': <function Boss.print at 0x016D4078>, '__weakref__': <attribute '__weakref__' of 'Boss' objects>, '__dict__': <attribute '__dict__' of 'Boss' objects>, 'func': <function Boss.func at 0x016D4030>, '__module__': '__main__'}
我们采用_boss__password这样的形式还是可以调用的。不过一般不建议这么使用,变形后的注意问题
- 这种隐藏并不是真正意思上的隐藏
- 变形的过程发生在类的定义发生一次,定义后的操作,不会变形
- 在继承中,如果父类不想子类覆盖子类自己的方法,可以将方法定义为私有,子类就不会继承到这些方法
3.2封装的意义
封装的意义为
- 封装数据:明确区分类内外的,控制外部对隐藏属性的操作
- 封装方法:隔离复杂度,只需要提供接口给其他人使用即可
3.3propetry函数
4.类的多态
4.1类的多态
4.2鸭子方法