一、组合

表示的一种什么有什么的关系

先来说一下,init的作用

  1. class Dog:
  2. def __init__(self, name, kind, hp, ad):
  3. self.name = name # 对象属性 属性
  4. self.kind = kind
  5. self.hp = hp
  6. self.ad = ad
  7. def bite(self, p):
  8. p.hp -= self.ad # 人掉血
  9. print('%s咬了%s一口,%s掉了%s点血' % (self.name, p.name, p.name, self.ad))

实例化A和B

  1. A = Dog('笨笨', 'teddy', 50, 10)
  2. B = Dog('花花')

A职员是老员工,他知道这个游戏,狗有什么属性。

B是新来的,假如没有init方法,B就随便传参数了,但是类方法执行时,会报错。

为了避免这个问题,在init方法里面,约束某些属性,必须要传,否则方法执行出错。

人狗大战游戏,现在需要增加武器

武器是人的一个属性,比如攻击力,磨损度,价格,名字,品级,技能

增加一个类

  1. class Weapon:
  2. def __init__(self, name, price, level, ad):
  3. self.name = name
  4. self.price = price
  5. self.level = level
  6. self.ad = ad * self.level # 升级之后,攻击就翻倍了
  7. self.wear = 20 # 默认的耐久度,实例化时,可以不用传
  8. def skill(self, dog): # 技能
  9. dog.hp -= self.ad
  10. print('%s受到了%s点的伤害,%s掉了%s点血' % (dog.name, self.name, dog.name, self.ad))

实例化一个武器

  1. axe = Weapon('斧头', 1000, 100, 1) # 斧头

二、组合实例

1.人狗大战

武器给谁装备呢?武器需要花钱买吧,那么就需要玩家充钱,现在加一个充钱功能

完整代码如下:

  1. class Person:
  2. def __init__(self, name, sex, hp, ad):
  3. self.name = name # 对象属性 属性
  4. self.sex = sex
  5. self.hp = hp # 血量
  6. self.ad = ad # 攻击力
  7. self.money = 0 # 金额
  8. self.arms = None # 默认武器为None<br>
  9. def attack(self, d):
  10. d.hp -= self.ad
  11. print('%s攻击了%s,%s掉了%s点血' % (self.name, d.name, d.name, self.ad))
  12. def pay(self): # 充值
  13. money = int(input('请输入您要充值的金额:'))
  14. self.money += money
  15. print('您的余额是:%s' % self.money)
  16. def wear(self, weapon): # 装备武器
  17. if self.money >= weapon.price:
  18. self.arms = weapon # 组合 给人装备了武器
  19. self.money -= weapon.price
  20. print('购买成功,您已经顺利装备了%s' % weapon.name)
  21. else:
  22. print('余额不足,请充值!')
  23. def attack_with_weapon(self, dog): # 拿武器攻击狗
  24. if 'arms' in self.__dict__ and self.arms != None: # 如果武器属性在实例属性字典里,并且属性不为空
  25. self.arms.skill(dog) # 使用武器攻击狗
  26. else:
  27. print('请先装备武器')
  28. class Dog:
  29. def __init__(self, name, kind, hp, ad):
  30. self.name = name # 对象属性 属性
  31. self.kind = kind
  32. self.hp = hp
  33. self.ad = ad
  34. def bite(self, p):
  35. p.hp -= self.ad # 人掉血
  36. print('%s咬了%s一口,%s掉了%s点血' % (self.name, p.name, p.name, self.ad))
  37. class Weapon: # 武器
  38. def __init__(self, name, price, level, ad):
  39. self.name = name # 武器名
  40. self.price = price # 价格
  41. self.level = level # 等级
  42. self.ad = ad * self.level # 升级之后,攻击就翻倍了
  43. self.wear = 20 # 默认的耐久度,实例化时,可以不用传
  44. def skill(self, dog): # 技能,攻击狗
  45. dog.hp -= self.ad # 狗掉血
  46. print('%s受到了%s点的伤害,%s掉了%s点血' % (dog.name, self.name, dog.name, self.ad))

实例化武器,玩家购买武器,攻击狗

  1. alex = Person('a_sb', '不详', 1, 5)
  2. boss_jin = Person('金老板', '女', 20, 50)
  3. teddy = Dog('笨笨', 'teddy', 50, 10)
  4. axe = Weapon('斧头', 1000, 100, 1) # 斧头
  5. alex.pay() # 充值
  6. alex.wear(axe) # 装备武器斧头
  7. alex.arms.skill(teddy) # 使用斧头攻击狗

执行输出:

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635250-11b62c82-608d-4945-82b9-8d4d3b0d23ad.png)

注意:

不能加类静态变量meny = 0

否则玩家充钱了,别的玩家就可以使用了

int之后,不需要strip()一下,int会自动去除空格

这样写是一次性的,写一个while循环,显示菜单执行

实例化部分,改成如下:

  1. alex = Person('a_sb', '不详', 1, 5)
  2. boss_jin = Person('金老板', '女', 20, 50)
  3. teddy = Dog('笨笨', 'teddy', 50, 10)
  4. axe = Weapon('斧头', 1000, 100, 1) # 斧头
  5. lst = ['攻击', '充值', '装备武器', '使用武器攻击']
  6. while True:
  7. for index, value in enumerate(lst, 1):
  8. print(index, value)
  9. num = int(input('请选择操作序号 >>>'))
  10. if num == 1:
  11. alex.attack(teddy)
  12. elif num == 2:
  13. alex.pay()
  14. elif num == 3:
  15. print('装备前余额 %s' % alex.money)
  16. alex.wear(axe)
  17. print('装备后余额 %s' % alex.money)
  18. elif num == 4:
  19. alex.attack_with_weapon(teddy)
  20. else:
  21. print('无效的序号')

执行输出:

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635228-157dd87b-dafe-41dd-865d-263c385016df.png)

修改实例化部分,代码如下:

  1. alex.pay() # 充值
  2. alex.wear(axe) # 装备武器斧头
  3. print(alex.__dict__) # 查看alex的属性
  4. print(axe) # 查看aex

执行输出:

请输入您要充值的金额:2000

您的余额是:2000

购买成功,您已经顺利装备了斧头

{‘sex’: ‘不详’, ‘hp’: 1, ‘name’: ‘asb’, ‘ad’: 5, ‘arms’: <_main.Weapon object at 0x00000250D273C6A0>, ‘money’: 1000}

<__main__.Weapon object at 0x00000250D273C6A0>

可以发现alex的arms属性和axe的内存地址,是一摸一样的。

skill方法,只有武器才有,人是不能直接调用的。但是,人一旦装备上了武器,就可以执行skill方法。

关键点,就在于以下一段代码

  1. self.arms = weapon # 组合 给人装备了武器

直接给人加了一个属性arms,注意,等式右边的weapon是一个武器对象

所以就可以使用武器攻击狗

  1. self.arms.skill(dog)

self也就是实例对象,比如alex

当一个类的对象作为另一个类的属性,说明这2个类组合在一起了。

那么类就可以使用另外一个类的属性和方法了。

一般说组合 ,是指2个类的组合

修改实例化部分

  1. alex = Person('a_sb', '不详', 1, 5)
  2. boss_jin = Person('金老板', '女', 20, 50)
  3. teddy = Dog('笨笨', 'teddy', 50, 10)
  4. axe = Weapon('斧头', 1000, 100, 1) # 斧头
  5. alex.pay() # 充值
  6. alex.wear(axe) # 装备武器斧头
  7. print(alex.arms.__dict__) # 查看axe实例,也就是Weapon类的所有属性
  8. alex.arms.skill(teddy) # 执行

执行输出:

请输入您要充值的金额:2000

您的余额是:2000

购买成功,您已经顺利装备了斧头

{‘price’: 1000, ‘ad’: 100, ‘level’: 100, ‘wear’: 20, ‘name’: ‘斧头’}

笨笨受到了斧头点的伤害,笨笨掉了100点血

为什么会用组合 :独立的对象不能发挥他的作用,必须依赖一个对象

比如上面的例子,斧头不能够攻击狗,它依赖人来执行。

修改实例化部分

  1. alex = Person('a_sb', '不详', 1, 5)
  2. boss_jin = Person('金老板', '女', 20, 50)
  3. teddy = Dog('笨笨', 'teddy', 50, 10)
  4. axe = Weapon('斧头', 1000, 100, 1) # 斧头
  5. axe.skill(teddy) # 直接用斧头攻击狗

执行输出:

笨笨受到了斧头点的伤害,笨笨掉了100点血

这样就不对了

人还可以装备2件武器

  1. alex = Person('a_sb', '不详', 1, 5)
  2. boss_jin = Person('金老板', '女', 20, 50)
  3. teddy = Dog('笨笨', 'teddy', 50, 10)
  4. axe = Weapon('斧头', 1000, 100, 1) # 斧头
  5. knife = Weapon('刀', 1000, 100, 1) # 刀
  6. alex.pay() # 充值
  7. alex.wear(axe) # 装备武器斧头
  8. alex.wear(knife) # 装备武器刀
  9. alex.arms.skill(teddy) # 执行攻击

执行输出:

请输入您要充值的金额:2000

您的余额是:2000

购买成功,您已经顺利装备了斧头

购买成功,您已经顺利装备了刀

笨笨受到了刀点的伤害,笨笨掉了100点血

2.圆环

圆形类

写一个圆环类 组合 圆形类 去完成 计算圆环的面积和周长

一个类的对象作为另一个类对象的属性

圆环中有圆

圆形类 : 计算圆形面积 和 周长

圆环类 :

圆环的周长 : 大圆周长加小圆周长

圆环的面积 : 大圆的面积 - 小圆的面积

看昨天写的圆环

  1. from math import pi
  2. class Ring1:
  3. def __init__(self, out_r, in_r):
  4. self.out_r = out_r
  5. self.in_r = in_r
  6. def cal_area(self):
  7. return abs(pi * self.out_r ** 2 - pi * self.in_r ** 2)
  8. def cal_perimeter(self):
  9. return pi * self.out_r * 2 + pi * self.in_r * 2
  10. r1 = Ring1(10, 5)
  11. print(r1.cal_area())
  12. print(r1.cal_perimeter())

执行输出:

235.61944901923448

94.24777960769379

改成组合方式

  1. from math import pi
  2. class Circle: # 圆形的面积公式不会变
  3. def __init__(self, r):
  4. self.r = r
  5. def cal_area(self): # 面积
  6. return pi * self.r ** 2
  7. def cal_perimeter(self): # 周长
  8. return pi * self.r * 2
  9. class Ring2: # 圆环
  10. def __init__(self, out_r, in_r):
  11. self.out_circle = Circle(out_r) # 实例化圆作为圆环的大圆
  12. self.in_circle = Circle(in_r) # 实例化圆,作为圆环的小圆
  13. def area(self): # 圆环面积<br> #用绝对值,保证结果不为负数
  14. return abs(self.out_circle.cal_area() - self.in_circle.cal_area()) # 大圆面积 - 小圆面积
  15. def cal_perimeter(self):
  16. return self.out_circle.cal_perimeter() + self.in_circle.cal_perimeter() # 大圆周长 + 小圆周长
  17. r1 = Ring2(10, 5) # 实例化圆环,传入大圆和小圆的半径
  18. print(r1.area())
  19. print(r1.cal_perimeter())

执行输出:

235.61944901923448

94.24777960769379

在这个例子中,圆环包含了圆,而圆的面积和周长公式,和圆环有点类似。

所以,可以把圆,这个对象,作为圆环类对象的属性,这就是组合。

3.老师和班级

老师

属性:姓名 年龄 性别 班级 : s11

班级

属性:班级名 班级人数 科目 性质

  1. class Clas:
  2. def __init__(self, name, num, course, type):
  3. self.name = name
  4. self.num = num
  5. self.course = course
  6. self.type = type
  7. class Teacher:
  8. def __init__(self, name, sex, age):
  9. self.name = name
  10. self.sex = sex
  11. self.age = age
  12. py11 = Clas('超级无敌s11', 89, 'python', '脱产全栈') # 实例化一个班级
  13. print(py11.course) # 查看课程
  14. boss_jin = Teacher('太白', '?', 40) # 实例化一个老师

执行输出:

python

那么老师怎么和班级关联呢?

直接给Teacher类加一个属性cls,表示班级

  1. class Teacher:
  2. def __init__(self, name, sex, age, cls):
  3. self.name = name
  4. self.sex = sex
  5. self.age = age
  6. self.cls = cls # 班级

实例化时,最后一个参数,直接传对象py11

  1. py11 = Clas('超级无敌s11', 89, 'python', '脱产全栈') # 实例化一个班级
  2. print(py11.course) # 查看课程
  3. boss_jin = Teacher('太白', '?', 40, py11) # 实例化一个老师
  4. print(boss_jin.cls.course) # 查看课程
  5. print(boss_jin.cls.__dict__) # 查看py11的所有属性

执行输出:

python

python

{‘num’: 89, ‘course’: ‘python’, ‘type’: ‘脱产全栈’, ‘name’: ‘超级无敌s11’}

如果人数变了,Teacher也能感知到

  1. py11 = Clas('超级无敌s11', 88, 'python', '脱产全栈') # 实例化一个班级
  2. print(py11.course) # 查看课程
  3. boss_jin = Teacher('太白', '?', 40, py11) # 实例化一个老师
  4. print(boss_jin.cls.num) # 查看人数

执行输出:

python

88

通过实例化,可以直接组合2个类。

在这个例子中,Teacher通过cls属性,得到了py11对象的所有属性以及方法。

这是组合的第二种使用方式

三、初识面向对象小结

面向对象的思想

不关注程序执行的过程

关心的是一个程序中的角色,以及角色与角色之间的关系

在python中,一切皆对象

类:list

对象,实例: [1,2,3,4,5]

看list的源代码

  1. class list(object):
  2. """
  3. list() -> new empty list
  4. list(iterable) -> new list initialized from iterable's items
  5. """

发现,list也是一个对象,它有很多方法

类是给别人使用的,比如list.append()

类转换为对象,是实例化的过程

实例化的过程

  1. 创建一个对象
  2. init给对象添加一些属性,对象默认的名字self

将self所指向的内存空间返回给实例化它的地方

使用这个对象可以找到两个东西

  • 对象所在的内存空间中存储的属性
  • 类对象指针,指向类中所有方法和静态属性

对象找名字的时候:先找自己内存空间中的,再找类的

对象没有权利修改类中的静态变量和方法,比如self.类静态变量

如果修改了,那么就存在自己的对象空间里面

用类名操作静态变量(属性),可以让全局生效

类名:实例化对象 调用静态属性 执行方法

交互:对象可以作为参数传递给类中的方法

组合:对象可以作为一个对象的属性—什么有什么的关系

处处都是组合和交互

  1. class B:pass
  2. class A:
  3. def func(self,aaa):
  4. print(aaa)
  5. a = A()
  6. b = B()
  7. a.func(b)

执行输出:

<__main__.B object at 0x000002CD1677C780>
  1. class B:pass
  2. class A:
  3. def func(self,aaa):
  4. print(aaa)
  5. a = A()
  6. b = B()
  7. a.func('666')

执行输出:666

这也是组合,666是str对象,它也是交互,它是python内置的对象和自定义的对象组合

  1. class B:pass
  2. class A:
  3. def func(self,aaa):
  4. self.aaa = aaa
  5. a = A()
  6. b = B()
  7. a.func('666') # 传值给aaa
  8. print(a.aaa)

执行输出:

666

  1. class B:pass
  2. class A:
  3. def func(self,aaa):
  4. self.aaa = aaa
  5. a = A()
  6. b = B()
  7. a.func('666')
  8. print(a.aaa.startswith('6'))

执行输出:True

四、初识继承

面向对象的三大特性:

继承,多态,封装

这3大特性是所有面向对象语言特点

比如游戏的例子

人物:名字,角色,性别,职业,技能

治疗

回血

医生

护士

输出

防御

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635249-79673c46-1d5f-47dc-b3c6-081d3e85afc8.png)

看如下代码:

  1. class Person:
  2. def __init__(self, name, hp, ad):
  3. self.name = name
  4. self.hp = hp
  5. self.ad = ad
  6. class Dog:
  7. def __init__(self, name, hp, ad):
  8. self.name = name
  9. self.hp = hp
  10. self.ad = ad

Person和dog有相同的属性,那么应该有某种联系

类与类直接的关系:什么是什么的关系

  1. class Parent:pass
  2. class Son(Parent):pass #继承关系,Son继承了Parent
  3. print(Son.__bases__) #内置的属性,查看父类
  4. print(Parent.__bases__)

执行输出:

(,)

(,)

可以看出Son的父类是Parent,Parent的父类是object

在python3中,所有的类都会默认继承object类

继承了object类的所有类都是新式类

如果一个类没有继承任何父类,那么bases属性就会显示

继承一般有2种:单继承和多继承

单继承

  1. class Parent:pass
  2. class Son(Person):pass

多继承

  1. class Parent1:pass
  2. class Parent2(Parent1):pass
  3. class Parent3:pass
  4. class Son(Parent2,Parent3):pass # 继承关系
  5. print(Parent2.__bases__)
  6. print(Son.__bases__)

执行输出:

(,)

(, )

父类 :别名有2个,基类,超类

子类 :别名 派生类

先有了人 狗两个类

发现两个类有相同的属性、方法

人和狗 都是游戏里的角色

抽象出一个animal类型

继承

  1. class Animal:
  2. role = 'Animal'
  3. def __init__(self,name,hp,ad):
  4. self.name = name # 对象属性 属性
  5. self.hp = hp #血量
  6. self.ad = ad #攻击力
  7. class Person(Animal):pass
  8. class Dog(Animal):pass
  9. alex = Person()

执行报错:

TypeError: init() missing 3 required positional arguments: ‘name’, ‘hp’, and ‘ad’

缺少3个参数,继承了父类的init方法

再次实例化

  1. alex = Person('alex',10,5)
  2. print(alex) # 查看父类
  3. print(alex.__dict__) # 查看属性

执行输出:

<__main__.Person object at 0x000001DA0687C7B8>

{‘hp’: 10, ‘name’: ‘alex’, ‘ad’: 5}

添加狗实例

  1. alex = Person('alex',10,5)
  2. print(alex)
  3. print(alex.__dict__)
  4. dog = Dog('teddy',100,20)
  5. print(dog)
  6. print(dog.__dict__)

执行输出:

<__main__.Person object at 0x0000015E6983C710>

{‘ad’: 5, ‘name’: ‘alex’, ‘hp’: 10}

<__main__.Dog object at 0x0000015E6983C7B8>

{‘ad’: 20, ‘name’: ‘teddy’, ‘hp’: 100}

自个,有各自的属性

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635255-651ad46a-ee12-4c2b-90f0-631c932e922d.png)
  1. class Animal:
  2. role = 'Animal'
  3. def __init__(self,name,hp,ad):
  4. self.name = name # 对象属性 属性
  5. self.hp = hp #血量
  6. self.ad = ad #攻击力
  7. def eat(self):
  8. print('%s吃药回血了'%self.name)
  9. class Person(Animal):
  10. r = 'Person'
  11. def attack(self,dog): # 派生方法
  12. print("%s攻击了%s"%(self.name,dog.name))
  13. def eat2(self):
  14. print('执行了Person类的eat方法')
  15. self.money = 100
  16. self.money -= 10
  17. self.hp += 10
  18. class Dog(Animal):
  19. def bite(self,person): # 派生方法
  20. print("%s咬了%s" % (self.name, person.name))
  21. alex = Person('alex',10,5)
  22. dog = Dog('teddy',100,20)
  23. alex.attack(dog) #继承中的派生方法
  24. alex.eat() #继承父类方法自己没有同名方法
  25. alex.eat2()
  26. dog.eat()

执行输出:

alex攻击了teddy

alex吃药回血了

执行了Person类的eat方法

teddy吃药回血了

init始终在父类里面,执行了alex实例化,返回给alex

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635343-c5dc7916-0da4-4bd8-a4d6-fcbec9e2765a.png)

alex实例化时,创建一个实例命令空间

alex.attack(dog) 直接调用,因为实例空间中存在

alex.eat() 实例空间找不到,取找类对象指针,指针指向父类Person,从此类中找到了eat方法。

….

对象使用名字的顺序

  1. alex = Person('alex',10,5)
  2. alex.eat = 'aaa' #增加一个不存在的方法,名字和父类方法名一样
  3. print(alex.eat)

指向输出:

aaa

  1. alex = Person('alex',10,5)
  2. alex.eat = 'aaa'
  3. print(alex.eat())

指向报错:

TypeError: ‘str’ object is not callable

因为此时eat是字符串,不是方法

对象使用名字顺序:

先找对象自己内存空间中的,再找对象自己类中的,再找父类中的

经典面试题

  1. class Parent:
  2. def func(self):
  3. print('in parent func')
  4. def __init__(self):
  5. self.func()
  6. class Son(Parent):
  7. def func(self):
  8. print('in son func')
  9. s = Son()

上面的代码执行的是son还是parent中的方法?

执行输出:

in son func

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635386-51466cf5-b077-415a-97fd-e1337957a9ac.png)

分析:

s = Son() 创建实例命名空间s

Son继承了Parent,类对象指针指向Son和Parent

Son没有init方法,它继承了Parent的init方法。

执行init方法,此时self是指向Son的

执行self.func()

根据查找顺序,自己->自己的类->父类

由于自己没有,从类中找,发现了,执行func

最终输出in son func

总结:

self.名字的时候,不要看self当前在哪个类里,要看这个self到底是谁的对象

来一张大神的图片

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597647635401-6a6fa3d3-bd8b-4ebf-966d-de2da12f54c0.png)

作业 :

读博客

把实例全走一遍

默写 圆环类与圆类组合

圆环类与圆类组合

  1. from math import pi
  2. class Circle: # 圆形的面积公式不会变
  3. def __init__(self, r):
  4. self.r = r
  5. def cal_area(self): # 面积
  6. return pi * self.r ** 2
  7. def cal_perimeter(self): # 周长
  8. return pi * self.r * 2
  9. class Ring2: # 圆环
  10. def __init__(self, out_r, in_r):
  11. self.out_circle = Circle(out_r) # 实例化圆作为圆环的大圆
  12. self.in_circle = Circle(in_r) # 实例化圆,作为圆环的小圆
  13. def area(self): # 圆环面积<br> #用绝对值,保证结果不为负数
  14. return abs(self.out_circle.cal_area() - self.in_circle.cal_area()) # 大圆面积 - 小圆面积
  15. def cal_perimeter(self):
  16. return self.out_circle.cal_perimeter() + self.in_circle.cal_perimeter() # 大圆周长 + 小圆周长
  17. r1 = Ring2(10, 5) # 实例化圆环,传入大圆和小圆的半径
  18. print(r1.area())
  19. print(r1.cal_perimeter())