python 全栈开发,Day18(对象之间的交互,类命名空间与对象,实例的命名空间,类的组合用法)
一、对象之间的交互
现在我们已经有一个人类了,通过给人类一些具体的属性我们就可以拿到一个实实在在的人。
现在我们要再创建一个狗类,狗就不能打人了,只能咬人,所以我们给狗一个bite方法。
有了狗类,我们还要实例化一只实实在在的狗出来。
然后人和狗就可以打架了。现在我们就来让他们打一架吧!
创建一个狗类
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 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad def attack( self ): print ( '%s发起了一次攻击' % self .name) class Dog: role = 'person' # 静态属性 def __init__( self , name, kind, hp, ad): self .name = name # 对象属性 属性 self .kind = kind self .hp = hp self .ad = ad def bite( self ): print ( '%s咬了人一口' % self .name) alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) alex.attack() # 相当于执行Person.attack(alex) boss_jin.attack() # 相当于执行Person.attack(boss_jin) teddy.bite() |
执行输出:
a_sb发起了一次攻击
金老板发起了一次攻击
笨笨咬了人一口
那么问题来了,人发起了一次攻击,他攻击了谁?
交互 teddy打alex一下
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 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad def attack( self ): print ( '%s发起了一次攻击' % self .name) class Dog: role = 'person' # 静态属性 def __init__( self , name, kind, hp, ad): self .name = name # 对象属性 属性 self .kind = kind self .hp = hp self .ad = ad def bite( self ,people): #people是变量名,它是一个对象 people.hp - = self .ad #人掉血 print ( '%s咬了%s一口,%s掉了%s点血' % ( self .name,people.name,people.name, self .ad)) #由于people是对象,取name就是people.name alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) teddy.bite(alex) #alex是一个对象,把对象传进去了 print (alex.hp) #查看alex的血 |
执行输出:
笨笨咬了a_sb一口,a_sb掉了10血
-9
再完善人攻击狗的技能
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 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad def attack( self ,dog): dog.hp - = self .ad print ( '%s攻击了%s,%s掉了%s点血' % ( self .name,dog.name,dog.name, self .ad)) class Dog: role = 'person' # 静态属性 def __init__( self , name, kind, hp, ad): self .name = name # 对象属性 属性 self .kind = kind self .hp = hp self .ad = ad def bite( self ,people): #people是变量名,它是一个对象 people.hp - = self .ad #人掉血 print ( '%s咬了%s一口,%s掉了%s点血' % ( self .name,people.name,people.name, self .ad)) #由于people是对象,取name就是people.name alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) teddy.bite(alex) #alex是一个对象,把对象传进去了 print (alex.hp) #查看alex的血 alex.attack(teddy) print (teddy.hp) #查看teddy的血 |
执行输出:
笨笨咬了a_sb一口,a_sb掉了10点血
-9
a_sb攻击了笨笨,笨笨掉了5点血
45
这款游戏,还可以丰富一点,比如玩家有4个技能,可以选择
等学了模块,可以做随机事件
比如暴击,中毒,每秒掉血等等...
交互到这里就说完了
方法可以传参,参数可以是对象。
交互: 人和狗的互相残杀
类中的方法的传参与调用
二、类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
其中类的数据属性是共享给所有对象的
而类的动态属性是绑定到所有对象的
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
上面的例子,看下面2句话
1 2 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) alex.attack(teddy) |
有一个问题
alex是如何找到Person类的?
因为是Person是实例化了alex,那么实例化过程中,发生了什么呢?
查看对象的属性
1 | print (alex.__dict__) |
{'hp': -9, 'sex': '不详', 'name': 'a_sb', 'ad': 5}
昨天的内容讲到,调用属性,需要__dict__中存在,才可以以调用
那这....
在面向过程中,代码执行时从上向下执行
类并不是完全这样执行的,有些步骤跳过了。
直接Person.role 就可以调用类表里
1 | print (Person.role) |
执行输出:person
所以说类里面的 方法,在实例化之前,就已经加载到内存中了
类代码执行,会跳过某些步骤
类是感知不到它有多少个方法
每当实例化之后,对象和类是单向关系,只能是对象取找类
看下面一张图
代码执行顺序:
1.加载类名Person
2.加载类静态属性role='person'
3.加载类初始函数__init__,注意,它不执行代码
4.加载类方法attack,注意,它不执行代码
5.实例化对象alex
6.执行类方法attack
7.打印对象alex的属性
执行1~4之后,会有一个类命名空间Person,它存储了3个变量
执行第5步时,开辟一个实例命名空间alex,它存储了4个变量,里面还有一个隐藏的变量,类对象指针。
它指向了类命名空间Person以及自己的实例命名空间
在初始化方法里面的self,代码实例本身,也就是实例命名空间alex
执行第6步。先找自己的内存空间,再找到类对象指针,再根据类对指针找到类 再通过类找到attack
第7步,打印实例命名空间的属性。
对象的内存空间里 是不是只包含init方法里面创建的所有属性?
加一个age属性试试
1 2 3 4 5 | alex = Person( 'a_sb' , '不详' , 1 , 5 ) alex.attack() alex.age = 18 #此时init没有age #Person实例化alex print (alex.__dict__) |
执行输出:
a_sb发起了一次攻击
{'ad': 5, 'age': 18, 'name': 'a_sb', 'sex': '不详', 'hp': 1}
输出结果里面有age变量了
这就说明,可以从外面向里面添加属性
类方法,也可以给self加参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad def attack( self ): self .hobby = 'girl' #增加属性 print ( '%s发起了一次攻击' % self .name) alex = Person( 'a_sb' , '不详' , 1 , 5 ) alex.attack() alex.age = 18 #此时init没有age #Person实例化alex print (alex.__dict__) |
执行输出:
a_sb发起了一次攻击
{'sex': '不详', 'name': 'a_sb', 'hp': 1, 'hobby': 'girl', 'age': 18, 'ad': 5}
对象内存空间里: 只存储对象的属性,而不存储方法和静态属性
查询对象内存空间,使用__dict__
为什么不存储类方法呢?假如类有50方法,都存一份?太浪费空间了。
为了节省空间,所以不存储类方法
方法和静态属性都存储在类的内存空间中
为了节省内存,让多个对象去恭喜类中的资源
当类空间不够时,会自动扩容
静态属性和方法是共享的
对象属性是独有的
对象不能有自己特有的方法
人狗大战里面再升级一下
人物类型里面有:
输出类:攻击高,血低
坦克类:血低,攻击低
治疗类:攻击低,治疗高
那么每一个类,都是一个单独的类。
假如治疗类里面有医生和护士
医生, 只能做手术
护士,只能包扎
所以医生和护士,必须是不同的类才行。不能用同一个类,因为方法不通。
对象和对象之间,方法不同时,那么它们一定是不同的类
在init里面增加和方法名同名的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad self .attack = 'haha' #增加和方法名同名的属性 def attack( self ): print ( '%s发起了一次攻击' % self .name) alex = Person( 'a_sb' , '不详' , 1 , 5 ) alex.attack() print (alex.__dict__) |
执行报错:
TypeError: 'str' object is not callable
为什么呢?因为alex实例命名空间里面找不到attack,所以它从类命名空间中找,但是此时attack变成字符串了,它是被不能执行的
对象查找顺序:先找自己内存空间,再找类的内存空间中的
2个对象,alex和boss_jin。当alex修改类静态属性时,boss_jin调用时,也会更改吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp self .ad = ad def attack( self ): print ( '%s发起了一次攻击' % self .name) alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 50 , 20 ) alex.role = 'dog' #修改类静态属性 print (alex.role) #查看类静态属性 print (boss_jin.role) |
执行输出:
dog
person
what?为什么没有改变,不是说类命名空间,是共享的吗?
但凡是对象操作类属性,它是没有权利的,它只能存储在自己内存空间里面
比如模拟人生的游戏
里面有一家人,爸爸妈妈出去工作赚钱,女儿和儿子上学,要花钱。
赚的钱,是共享的,大家都可以使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person: money = 0 def __init__( self ,name): self .name = name def work( self ): self .money + = 1000 print ( self .name, '工作,赚了%s快钱' % self .money) father = Person( 'father' ) mother = Person( 'mother' ) father.work() mother.work() print (Person.money) #查看钱 |
执行输出:
father 工作,赚了1000快钱
mother 工作,赚了1000快钱
0
但是钱没有涨?为什么?
因为对象获取了类属性money,直接使用self.money += 1000 ,就相当于在本实例空间操作了。
但是类命名空间并没有改变,,它还是0
怎么解决这个问题呢?
直接用类属性修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person: money = 0 def __init__( self ,name): self .name = name def work( self ): Person.money + = 1000 #类属性修改 print ( self .name, '工作,赚了%s快钱' % self .money) father = Person( 'father' ) mother = Person( 'mother' ) father.work() mother.work() print (Person.money) #查看钱 |
执行输出:
father 工作,赚了1000快钱
mother 工作,赚了2000快钱
2000
对于静态属性的修改,应该使用类名直接修改,就不会出现不一致的情况
因为类属性是共享的。
小作业:
写一个类,完成一个功能,可以统计这个类有几个对象
代码如下:
1 2 3 4 5 6 7 8 9 | class Foo: count = 0 def __init__( self ): Foo.count + = 1 f1 = Foo() f2 = Foo() f3 = Foo() print (Foo.count) |
执行输出: 3
如果对象名字一样呢?结果也是一样的
1 2 3 4 | f1 = Foo() f1 = Foo() f1 = Foo() print (Foo.count) |
执行输出: 3
此时此刻,只有一个f1,之前的被覆盖了。
实例化一次,就会有一个对象。
小测试,看如下代码,最终money会输出多少?2000还是0?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person: money = [ 0 ] def __init__( self ,name): self .name = name def work( self ): self .money[ 0 ] + = 1000 print ( self .name, '工作,赚了1000快钱' ) father = Person( 'father' ) mother = Person( 'mother' ) father.work() mother.work() print (Person.money) #查看钱 |
执行输出:
father 工作,赚了1000快钱
mother 工作,赚了1000快钱
[2000]
为什么是2000 ?不是说但凡是对象操作类属性,它是没有权利的,它只能存储在自己内存空间里面
what?
第一次类初始化是,money[0] =0。
当father实例化之后,money[0]的指针就失效了,变成了1000
当mother实例化之后,money[0]的指针就失效了,变成了2000
最终结果为2000
因为list是可变类型,它有自己独立的内存空间。当实例改变时,它就改变了。所以最后调用时,money的值为2000
再来一个小测试,还是上面的例子,修改了一下代码
那么money是0还是2000?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person: money = [ 0 ] def __init__( self ,name): self .name = name def work( self ): self .money = [Person.money[ 0 ] + 1000 ] print ( self .name, '工作,赚了1000快钱' ) father = Person( 'father' ) mother = Person( 'mother' ) father.work() mother.work() print (Person.money) #查看钱 |
执行输出:
father 工作,赚了1000快钱
mother 工作,赚了1000快钱
[0]
what?怎么是0了?它不是列表吗?它不是改了吗?
城市套路深我想回农村...
请注意表达式self.money = [Person.money[0] + 1000]
等式先计算右边的,Person.money[0]之间用类名.属性名调用,那么应该是[0 + 1000],最终结果为[1000]。
将结果赋值给self.money,它是实例本身调用money,所以修改的值[1000],存放在本实例空间。
类属性,并没有被改变,所以最终调用类属性时,输出结果为[0]
有一个终极方法,一招制敌
不管时类里面还是类外面,统一使用如下方法修改
类名.静态变量名
总结:
实例查找变量的顺序:
先找自己的内存空间->再找到类对象指针->再根据类对象指针找到类->再通过类找
对象的内存空间里: 只存储对象的属性,而不存储方法和静态属性
方法和静态属性都存储在类的内存空间中
为了节省内存,让多个对象去共享类中的资源
对象属性是独有的,静态属性和方法是共享的
对象使用名字:先找自己内存空间中的,再找类的内存空间中的
类名.静态变量名 :对于静态属性的修改,应该使用类名直接修改
三、面向对象的实例
圆
计算圆的周长 2rπ (r表示半径)
计算圆的面积 r²π
现在有5个圆,半径分别是1,3,5,7,9
请使用面向对象编写出来
提示:π需要用python模块math里面的pi方法
math---数学函数
作用:提供函数完成特殊的数学运算。
pi表示圆周率
1 2 | from math import pi print (pi) |
执行输出:
3.141592653589793
代码如下:
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 | from math import pi class Circle: def __init__( self , r): self .r = r def cal_area( self ): ''' 计算圆面积的方法 :return:返回值是float数据类型的面积 ''' return pi * self .r * * 2 def cal_perimeter( self ): ''' 计算圆周长的方法 :return:返回值是float数据类型的周长 ''' return pi * self .r * 2 for i in range ( 1 , 10 , 2 ): #隔一个取一个,得到1,3,5,7,9 c1 = Circle(i) #实例化 print (c1.cal_area()) print (c1.cal_perimeter()) |
执行输出:
3.141592653589793
6.283185307179586
28.274333882308138
18.84955592153876
78.53981633974483
31.41592653589793
153.93804002589985
43.982297150257104
254.46900494077323
56.548667764616276
到这里
面向对象的基础就结束了
作业:
1 2 3 4 5 6 7 8 9 10 11 | 1. 计算圆环的面积和周长 2. 默写交互 3. 整理校园管里系统的思路 把需求中找到所有能找到的类,属性定义出来 比如,学生类,它的属性有姓名,性别,班级,课程 方法,查看自己所在的班级,查看所学课程 |
答案:
1.圆环面积和周长公式
大圆半径R,小圆半径r:
圆环的周长=大圆的周长+小圆的周长
=2Rπ+2rπ
=2π(R+r);
[圆环的面积=大圆的面积-小圆的面积
=R²π-r²π
=(R²-r²)π,
代码如下:
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 | from math import pi class Ring: def __init__( self , R, r): self .r = r self .R = R def cal_area( self ): ''' 计算圆环面积的方法 :return:返回值是float数据类型的面积 ''' return 2 * pi * ( self .R + self .r) def cal_perimeter( self ): ''' 计算圆环周长的方法 :return:返回值是float数据类型的周长 ''' return ( self .R * * 2 - self .r * * 2 ) * pi c1 = Ring( 10 , 6 ) # 实例化 print (c1.cal_area()) #面积 print (c1.cal_perimeter()) #周长 |
执行输出:
100.53096491487338
201.06192982974676
2.默认交互
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 | class Person: role = 'person' # 静态属性 def __init__( self , name, sex, hp, ad): self .name = name # 对象属性 属性 self .sex = sex self .hp = hp # 血量 self .ad = ad # 攻击力 def attack( self , d): d.hp - = self .ad print ( '%s攻击了%s,%s掉了%s点血' % ( self .name, d.name, d.name, self .ad)) class Dog: role = 'person' # 静态属性 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)) alex = Person( 'a_sb' , '不详' , 1 , 5 ) boss_jin = Person( '金老板' , '女' , 20 , 50 ) teddy = Dog( '笨笨' , 'teddy' , 50 , 10 ) teddy.bite(alex) print (alex.hp) boss_jin.attack(teddy) print (teddy.hp) |
3.题目需求链接
https://www.cnblogs.com/Eva-J/articles/7747946.html
【推荐】国内首个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