python 全栈开发,Day19(组合,组合实例,初识面向对象小结,初识继承)
一、组合
表示的一种什么有什么的关系
先来说一下,__init__的作用
1 2 3 4 5 6 7 8 9 10 | class Dog: def __init__( self , name, kind, hp, ad): self .name = name # 对象属性 属性 self .kind = kind self .hp = hp self .ad = ad def bite( self , p): p.hp - = self .ad # 人掉血 print ( '%s咬了%s一口,%s掉了%s点血' % ( self .name, p.name, p.name, self .ad)) |
实例化A和B
1 2 | A = Dog( '笨笨' , 'teddy' , 50 , 10 ) B = Dog( '花花' ) |
A职员是老员工,他知道这个游戏,狗有什么属性。
B是新来的,假如没有__init__方法,B就随便传参数了,但是类方法执行时,会报错。
为了避免这个问题,在__init__方法里面,约束某些属性,必须要传,否则方法执行出错。
人狗大战游戏,现在需要增加武器
武器是人的一个属性,比如攻击力,磨损度,价格,名字,品级,技能
增加一个类
1 2 3 4 5 6 7 8 9 10 11 | class Weapon: def __init__( self , name, price, level, ad): self .name = name self .price = price self .level = level self .ad = ad * self .level # 升级之后,攻击就翻倍了 self .wear = 20 # 默认的耐久度,实例化时,可以不用传 def skill( self , dog): # 技能 dog.hp - = self .ad print ( '%s受到了%s点的伤害,%s掉了%s点血' % (dog.name, self .name, dog.name, self .ad)) |
实例化一个武器
1 | axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 |
二、组合实例
1.人狗大战
武器给谁装备呢?武器需要花钱买吧,那么就需要玩家充钱,现在加一个充钱功能
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | class Person: def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp # 血量 self .ad = ad # 攻击力 self .money = 0 # 金额 self .arms = None # 默认武器为None<br> def attack( self , d): d.hp - = self .ad print ( '%s攻击了%s,%s掉了%s点血' % ( self .name, d.name, d.name, self .ad)) def pay( self ): # 充值 money = int ( input ( '请输入您要充值的金额:' )) self .money + = money print ( '您的余额是:%s' % self .money) def wear( self , weapon): # 装备武器 if self .money > = weapon.price: self .arms = weapon # 组合 给人装备了武器 self .money - = weapon.price print ( '购买成功,您已经顺利装备了%s' % weapon.name) else : print ( '余额不足,请充值!' ) def attack_with_weapon( self , dog): # 拿武器攻击狗 if 'arms' in self .__dict__ and self .arms ! = None : # 如果武器属性在实例属性字典里,并且属性不为空 self .arms.skill(dog) # 使用武器攻击狗 else : print ( '请先装备武器' ) class Dog: def __init__( self , name, kind, hp, ad): self .name = name # 对象属性 属性 self .kind = kind self .hp = hp self .ad = ad def bite( self , p): p.hp - = self .ad # 人掉血 print ( '%s咬了%s一口,%s掉了%s点血' % ( self .name, p.name, p.name, self .ad)) class Weapon: # 武器 def __init__( self , name, price, level, ad): self .name = name # 武器名 self .price = price # 价格 self .level = level # 等级 self .ad = ad * self .level # 升级之后,攻击就翻倍了 self .wear = 20 # 默认的耐久度,实例化时,可以不用传 def skill( self , dog): # 技能,攻击狗 dog.hp - = self .ad # 狗掉血 print ( '%s受到了%s点的伤害,%s掉了%s点血' % (dog.name, self .name, dog.name, self .ad)) |
实例化武器,玩家购买武器,攻击狗
1 2 3 4 5 6 7 8 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 alex.pay() # 充值 alex.wear(axe) # 装备武器斧头 alex.arms.skill(teddy) # 使用斧头攻击狗 |
执行输出:
注意:
不能加类静态变量meny = 0
否则玩家充钱了,别的玩家就可以使用了
int之后,不需要strip()一下,int会自动去除空格
这样写是一次性的,写一个while循环,显示菜单执行
实例化部分,改成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 lst = [ '攻击' , '充值' , '装备武器' , '使用武器攻击' ] while True : for index, value in enumerate (lst, 1 ): print (index, value) num = int ( input ( '请选择操作序号 >>>' )) if num = = 1 : alex.attack(teddy) elif num = = 2 : alex.pay() elif num = = 3 : print ( '装备前余额 %s' % alex.money) alex.wear(axe) print ( '装备后余额 %s' % alex.money) elif num = = 4 : alex.attack_with_weapon(teddy) else : print ( '无效的序号' ) |
执行输出:
修改实例化部分,代码如下:
1 2 3 4 | alex.pay() # 充值 alex.wear(axe) # 装备武器斧头 print (alex.__dict__) # 查看alex的属性 print (axe) # 查看aex |
执行输出:
请输入您要充值的金额:2000
您的余额是:2000
购买成功,您已经顺利装备了斧头
{'sex': '不详', 'hp': 1, 'name': 'a_sb', '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 2 3 4 5 6 7 8 9 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 alex.pay() # 充值 alex.wear(axe) # 装备武器斧头 print (alex.arms.__dict__) # 查看axe实例,也就是Weapon类的所有属性 alex.arms.skill(teddy) # 执行 |
执行输出:
请输入您要充值的金额:2000
您的余额是:2000
购买成功,您已经顺利装备了斧头
{'price': 1000, 'ad': 100, 'level': 100, 'wear': 20, 'name': '斧头'}
笨笨受到了斧头点的伤害,笨笨掉了100点血
为什么会用组合 :独立的对象不能发挥他的作用,必须依赖一个对象
比如上面的例子,斧头不能够攻击狗,它依赖人来执行。
修改实例化部分
1 2 3 4 5 6 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 axe.skill(teddy) # 直接用斧头攻击狗 |
执行输出:
笨笨受到了斧头点的伤害,笨笨掉了100点血
这样就不对了
人还可以装备2件武器
1 2 3 4 5 6 7 8 9 10 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) axe = Weapon( '斧头' , 1000 , 100 , 1 ) # 斧头 knife = Weapon( '刀' , 1000 , 100 , 1 ) # 刀 alex.pay() # 充值 alex.wear(axe) # 装备武器斧头 alex.wear(knife) # 装备武器刀 alex.arms.skill(teddy) # 执行攻击 |
执行输出:
请输入您要充值的金额:2000
您的余额是:2000
购买成功,您已经顺利装备了斧头
购买成功,您已经顺利装备了刀
笨笨受到了刀点的伤害,笨笨掉了100点血
2.圆环
圆形类
写一个圆环类 组合 圆形类 去完成 计算圆环的面积和周长
一个类的对象作为另一个类对象的属性
圆环中有圆
圆形类 : 计算圆形面积 和 周长
圆环类 :
圆环的周长 : 大圆周长加小圆周长
圆环的面积 : 大圆的面积 - 小圆的面积
看昨天写的圆环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from math import pi class Ring1: def __init__( self , out_r, in_r): self .out_r = out_r self .in_r = in_r def cal_area( self ): return abs (pi * self .out_r * * 2 - pi * self .in_r * * 2 ) def cal_perimeter( self ): return pi * self .out_r * 2 + pi * self .in_r * 2 r1 = Ring1( 10 , 5 ) print (r1.cal_area()) print (r1.cal_perimeter()) |
执行输出:
235.61944901923448
94.24777960769379
改成组合方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from math import pi class Circle: # 圆形的面积公式不会变 def __init__( self , r): self .r = r def cal_area( self ): # 面积 return pi * self .r * * 2 def cal_perimeter( self ): # 周长 return pi * self .r * 2 class Ring2: # 圆环 def __init__( self , out_r, in_r): self .out_circle = Circle(out_r) # 实例化圆作为圆环的大圆 self .in_circle = Circle(in_r) # 实例化圆,作为圆环的小圆 def area( self ): # 圆环面积<br> #用绝对值,保证结果不为负数 return abs ( self .out_circle.cal_area() - self .in_circle.cal_area()) # 大圆面积 - 小圆面积 def cal_perimeter( self ): return self .out_circle.cal_perimeter() + self .in_circle.cal_perimeter() # 大圆周长 + 小圆周长 r1 = Ring2( 10 , 5 ) # 实例化圆环,传入大圆和小圆的半径 print (r1.area()) print (r1.cal_perimeter()) |
执行输出:
235.61944901923448
94.24777960769379
在这个例子中,圆环包含了圆,而圆的面积和周长公式,和圆环有点类似。
所以,可以把圆,这个对象,作为圆环类对象的属性,这就是组合。
3.老师和班级
老师
属性:姓名 年龄 性别 班级 : s11
班级
属性:班级名 班级人数 科目 性质
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Clas: def __init__( self , name, num, course, type ): self .name = name self .num = num self .course = course self . type = type class Teacher: def __init__( self , name, sex, age): self .name = name self .sex = sex self .age = age py11 = Clas( '超级无敌s11' , 89 , 'python' , '脱产全栈' ) # 实例化一个班级 print (py11.course) # 查看课程 boss_jin = Teacher( '太白' , '?' , 40 ) # 实例化一个老师 |
执行输出:
python
那么老师怎么和班级关联呢?
直接给Teacher类加一个属性cls,表示班级
1 2 3 4 5 6 | class Teacher: def __init__( self , name, sex, age, cls ): self .name = name self .sex = sex self .age = age self . cls = cls # 班级 |
实例化时,最后一个参数,直接传对象py11
1 2 3 4 5 6 | py11 = Clas( '超级无敌s11' , 89 , 'python' , '脱产全栈' ) # 实例化一个班级 print (py11.course) # 查看课程 boss_jin = Teacher( '太白' , '?' , 40 , py11) # 实例化一个老师 print (boss_jin. cls .course) # 查看课程 print (boss_jin. cls .__dict__) # 查看py11的所有属性 |
执行输出:
python
python
{'num': 89, 'course': 'python', 'type': '脱产全栈', 'name': '超级无敌s11'}
如果人数变了,Teacher也能感知到
1 2 3 4 5 | py11 = Clas( '超级无敌s11' , 88 , 'python' , '脱产全栈' ) # 实例化一个班级 print (py11.course) # 查看课程 boss_jin = Teacher( '太白' , '?' , 40 , py11) # 实例化一个老师 print (boss_jin. cls .num) # 查看人数 |
执行输出:
python
88
通过实例化,可以直接组合2个类。
在这个例子中,Teacher通过cls属性,得到了py11对象的所有属性以及方法。
这是组合的第二种使用方式
三、初识面向对象小结
面向对象的思想
不关注程序执行的过程
关心的是一个程序中的角色,以及角色与角色之间的关系
在python中,一切皆对象
类:list
对象,实例: [1,2,3,4,5]
看list的源代码
1 2 3 4 5 | class list ( object ): """ list() -> new empty list list(iterable) -> new list initialized from iterable's items """ |
发现,list也是一个对象,它有很多方法
类是给别人使用的,比如list.append()
类转换为对象,是实例化的过程
实例化的过程
创建一个对象
__init__给对象添加一些属性,对象默认的名字self
将self所指向的内存空间返回给实例化它的地方
使用这个对象可以找到两个东西
对象所在的内存空间中存储的属性
类对象指针,指向类中所有方法和静态属性
对象找名字的时候:先找自己内存空间中的,再找类的
对象没有权利修改类中的静态变量和方法,比如self.类静态变量
如果修改了,那么就存在自己的对象空间里面
用类名操作静态变量(属性),可以让全局生效
类名:实例化对象 调用静态属性 执行方法
交互:对象可以作为参数传递给类中的方法
组合:对象可以作为一个对象的属性--什么有什么的关系
处处都是组合和交互
1 2 3 4 5 6 7 8 | class B: pass class A: def func( self ,aaa): print (aaa) a = A() b = B() a.func(b) |
执行输出:
<__main__.B object at 0x000002CD1677C780>
1 2 3 4 5 6 7 8 | class B: pass class A: def func( self ,aaa): print (aaa) a = A() b = B() a.func( '666' ) |
执行输出:666
这也是组合,666是str对象,它也是交互,它是python内置的对象和自定义的对象组合
1 2 3 4 5 6 7 8 9 | class B: pass class A: def func( self ,aaa): self .aaa = aaa a = A() b = B() a.func( '666' ) # 传值给aaa print (a.aaa) |
执行输出:
666
1 2 3 4 5 6 7 8 9 | class B: pass class A: def func( self ,aaa): self .aaa = aaa a = A() b = B() a.func( '666' ) print (a.aaa.startswith( '6' )) |
执行输出:True
四、初识继承
面向对象的三大特性:
继承,多态,封装
这3大特性是所有面向对象语言特点
比如游戏的例子
人物:名字,角色,性别,职业,技能
治疗
回血
医生
护士
输出
防御
看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 | class Person: def __init__( self , name, hp, ad): self .name = name self .hp = hp self .ad = ad class Dog: def __init__( self , name, hp, ad): self .name = name self .hp = hp self .ad = ad |
Person和dog有相同的属性,那么应该有某种联系
类与类直接的关系:什么是什么的关系
1 2 3 4 | class Parent: pass class Son(Parent): pass #继承关系,Son继承了Parent print (Son.__bases__) #内置的属性,查看父类 print (Parent.__bases__) |
执行输出:
(<class '__main__.Parent'>,)
(<class 'object'>,)
可以看出Son的父类是Parent,Parent的父类是object
在python3中,所有的类都会默认继承object类
继承了object类的所有类都是新式类
如果一个类没有继承任何父类,那么__bases__属性就会显示<class 'object'>
继承一般有2种:单继承和多继承
单继承
1 2 | class Parent: pass class Son(Person): pass |
多继承
1 2 3 4 5 6 | class Parent1: pass class Parent2(Parent1): pass class Parent3: pass class Son(Parent2,Parent3): pass # 继承关系 print (Parent2.__bases__) print (Son.__bases__) |
执行输出:
(<class '__main__.Parent1'>,)
(<class '__main__.Parent2'>, <class '__main__.Parent3'>)
父类 :别名有2个,基类,超类
子类 :别名 派生类
先有了人 狗两个类
发现两个类有相同的属性、方法
人和狗 都是游戏里的角色
抽象出一个animal类型
继承
1 2 3 4 5 6 7 8 9 10 | class Animal: role = 'Animal' def __init__( self ,name,hp,ad): self .name = name # 对象属性 属性 self .hp = hp #血量 self .ad = ad #攻击力 class Person(Animal): pass class Dog(Animal): pass alex = Person() |
执行报错:
TypeError: __init__() missing 3 required positional arguments: 'name', 'hp', and 'ad'
缺少3个参数,继承了父类的init方法
再次实例化
1 2 3 | alex = Person( 'alex' , 10 , 5 ) print (alex) # 查看父类 print (alex.__dict__) # 查看属性 |
执行输出:
<__main__.Person object at 0x000001DA0687C7B8>
{'hp': 10, 'name': 'alex', 'ad': 5}
添加狗实例
1 2 3 4 5 6 | alex = Person( 'alex' , 10 , 5 ) print (alex) print (alex.__dict__) dog = Dog( 'teddy' , 100 , 20 ) print (dog) 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Animal: role = '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): r = 'Person' def attack( self ,dog): # 派生方法 print ( "%s攻击了%s" % ( self .name,dog.name)) def eat2( self ): print ( '执行了Person类的eat方法' ) self .money = 100 self .money - = 10 self .hp + = 10 class Dog(Animal): def bite( self ,person): # 派生方法 print ( "%s咬了%s" % ( self .name, person.name)) alex = Person( 'alex' , 10 , 5 ) dog = Dog( 'teddy' , 100 , 20 ) alex.attack(dog) #继承中的派生方法 alex.eat() #继承父类方法自己没有同名方法 alex.eat2() dog.eat() |
执行输出:
alex攻击了teddy
alex吃药回血了
执行了Person类的eat方法
teddy吃药回血了
init始终在父类里面,执行了alex实例化,返回给alex
alex实例化时,创建一个实例命令空间
alex.attack(dog) 直接调用,因为实例空间中存在
alex.eat() 实例空间找不到,取找类对象指针,指针指向父类Person,从此类中找到了eat方法。
....
对象使用名字的顺序
1 2 3 | alex = Person( 'alex' , 10 , 5 ) alex.eat = 'aaa' #增加一个不存在的方法,名字和父类方法名一样 print (alex.eat) |
指向输出:
aaa
1 2 3 | alex = Person( 'alex' , 10 , 5 ) alex.eat = 'aaa' print (alex.eat()) |
指向报错:
TypeError: 'str' object is not callable
因为此时eat是字符串,不是方法
对象使用名字顺序:
先找对象自己内存空间中的,再找对象自己类中的,再找父类中的
经典面试题
1 2 3 4 5 6 7 8 9 10 11 | class Parent: def func( self ): print ( 'in parent func' ) def __init__( self ): self .func() class Son(Parent): def func( self ): print ( 'in son func' ) s = Son() |
上面的代码执行的是son还是parent中的方法?
执行输出:
in son func
分析:
s = Son() 创建实例命名空间s
Son继承了Parent,类对象指针指向Son和Parent
Son没有__init__方法,它继承了Parent的__init__方法。
执行__init__方法,此时self是指向Son的
执行self.func()
根据查找顺序,自己->自己的类->父类
由于自己没有,从类中找,发现了,执行func
最终输出in son func
总结:
self.名字的时候,不要看self当前在哪个类里,要看这个self到底是谁的对象
来一张大神的图片
作业 :
1 2 3 | 读博客 把实例全走一遍 默写 圆环类与圆类组合 |
圆环类与圆类组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from math import pi class Circle: # 圆形的面积公式不会变 def __init__( self , r): self .r = r def cal_area( self ): # 面积 return pi * self .r * * 2 def cal_perimeter( self ): # 周长 return pi * self .r * 2 class Ring2: # 圆环 def __init__( self , out_r, in_r): self .out_circle = Circle(out_r) # 实例化圆作为圆环的大圆 self .in_circle = Circle(in_r) # 实例化圆,作为圆环的小圆 def area( self ): # 圆环面积<br> #用绝对值,保证结果不为负数 return abs ( self .out_circle.cal_area() - self .in_circle.cal_area()) # 大圆面积 - 小圆面积 def cal_perimeter( self ): return self .out_circle.cal_perimeter() + self .in_circle.cal_perimeter() # 大圆周长 + 小圆周长 r1 = Ring2( 10 , 5 ) # 实例化圆环,传入大圆和小圆的半径 print (r1.area()) print (r1.cal_perimeter()) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix