python 全栈开发,Day17(初识面向对象)
一、引子
第一次参加工作,进入了一家游戏公司,公司需要开发一款游戏《人狗大战》
一款游戏,首先得把角色和属性定下来。
角色有2个,分别是人和狗
属性如下:
人 :昵称、性别、血、攻击力
狗 :名字、品种、血、攻击力
定义2个字典
1 2 3 4 | #人 person = { 'name' : 'xiao_Ming' , 'sex' : 'M' , 'hp' : 1 , 'ad' : 5 } #狗 dog = { 'name' : '旺财' , 'sex' : 'M' , 'hp' : 100 , 'ad' : 100 } |
首先是人攻击狗,定义个函数
1 2 3 4 5 | def attack(person,dog): #人攻击狗 print ( '{}攻击{}' . format (person[ 'name' ], dog[ 'name' ])) #狗掉血,狗的血量-人的攻击力 dog[ 'hp' ] - = person[ 'ad' ] |
执行函数
1 2 3 | attack(person,dog) #查看狗的血量 print (dog[ 'hp' ]) |
执行输出:
xiao_Ming攻击旺财
95
人攻击了狗,狗得反击吧,再定义一个函数
1 2 3 4 5 6 7 | def bite(dog,person): #狗咬人 print ( '{}咬了{}' . format (dog[ 'name' ], person[ 'name' ])) # 人掉血,人的血量-狗的攻击力 person[ 'hp' ] - = dog[ 'ad' ] #判断人的血量是否小于等于0 if person[ 'hp' ] < = 0 : print ( 'game over,{} win' . format (dog[ 'name' ])) |
执行函数
1 2 3 | bite(dog,person) #查看人的血量 print (person[ 'hp' ]) |
执行输出:
旺财咬了xiao_Ming
game over,旺财 win
-99
现在还只有一个玩家,有多个玩家怎么办,再加一个?
每添加一个人,就得创建一个字典
但是创造一个人物角色,没有血条,游戏就会有bug
所以,为了解决这个问题,需要定义一个模板,那么人的属性就固定下来了
定义2个函数,人和狗的模板
1 2 3 4 5 6 7 8 9 | def Person(name,sex,hp,ad): # 人模子 self = { 'name' :name, 'sex' :sex, 'hp' :hp, 'ad' : ad} return self def Dog(name,varieties,hp,ad): # 狗模子 self = { 'name' : name, 'varieties' : varieties, 'hp' : hp, 'ad' : ad} return self |
注意: self它不是关键字,只是一个变量而已。varieties表示品种
创建2个角色
1 2 | person1 = Person( 'xiao_Ming' , 'M' , 1 , 5 ) dog1 = Dog( '旺财' , 'teddy' , 100 , 100 ) |
可以发现,这里就规范了角色的属性个数,简化了创建角色的代码
执行狗咬人函数
1 2 | bite(dog1,person1) print (person1[ 'hp' ]) |
执行输出:
旺财咬了xiao_Ming
game over,旺财 win
-99
如果参数传的顺序乱了,游戏就会有bug
1 2 | attack(dog1,person1) print (person1[ 'hp' ]) |
执行输出:
旺财攻击xiao_Ming
-199
为了解决这个问题,需要把攻击函数放在人模子里面,咬人放在狗模板里面。
外部无法直接调用。
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 | def Person(name,sex,hp,ad): # 人模子 self = { 'name' :name, 'sex' :sex, 'hp' :hp, 'ad' : ad} def attack(dog): # 人攻击狗 #参数已经被self接收了,所以attack函数不需要接收2个参数,1个参数就够了 print ( '{}攻击{}' . format ( self [ 'name' ], dog[ 'name' ])) # 狗掉血,狗的血量-人的攻击力 dog[ 'hp' ] - = self [ 'ad' ] self [ 'attack' ] = attack #增加一个字典key print ( self ) #查看字典的值 return self def Dog(name,varieties,hp,ad): # 狗模子 self = { 'name' : name, 'varieties' : varieties, 'hp' : hp, 'ad' : ad} def bite(person): # 狗咬人 # 参数已经被self接收了,所以bite函数不需要接收2个参数,1个参数就够了 print ( '{}咬了{}' . format ( self [ 'name' ], person[ 'name' ])) # 人掉血,人的血量-狗的攻击力 person[ 'hp' ] - = self [ 'ad' ] # 判断人的血量是否小于等于0 if person[ 'hp' ] < = 0 : print ( 'game over,{} win' . format ( self [ 'name' ])) self [ 'bite' ] = bite return self #创建2个角色 person1 = Person( 'xiao_Ming' , 'M' , 1 , 5 ) dog1 = Dog( '旺财' , 'teddy' , 100 , 100 ) #执行人攻击狗函数,它是通过字典取值调用的。 person1[ 'attack' ](dog1) print (dog1[ 'hp' ]) |
执行输出:
{'name': 'xiao_Ming', 'sex': 'M', 'hp': 1, 'attack': <function Person.<locals>.attack at 0x000001F56180AAE8>, 'ad': 5}
xiao_Ming攻击旺财
95
字典里面的attack对应的值,是一个函数,也就是一个内存地址
把值取出来,传参就可以执行了
person1['attack'](dog1)
如果定义多个角色呢?
1 2 3 | person1 = Person( 'xiao_Ming' , 'M' , 1 , 5 ) person2 = Person( 'Zhang_san' , 'M' , 1 , 5 ) person3 = Person( 'Li_si' , 'M' , 1 , 5 ) |
执行输出:
{'name': 'xiao_Ming', 'hp': 1, 'ad': 5, 'sex': 'M', 'attack': <function Person.<locals>.attack at 0x000001EC7C3AAAE8>}
{'name': 'Zhang_san', 'hp': 1, 'ad': 5, 'sex': 'M', 'attack': <function Person.<locals>.attack at 0x000001EC7C3AAB70>}
{'name': 'Li_si', 'hp': 1, 'ad': 5, 'sex': 'M', 'attack': <function Person.<locals>.attack at 0x000001EC7C3AABF8>}
注意观察attack的值,它们对应的是不同的内存地址。
函数每执行一次,就会开辟一个新的命名空间,相互之间不受影响。
在命名空间内部,self是字典,接收参数,attack是函数,用来调用的。
上面的2个函数Person和Dog用了闭包
这就是用函数的方式完成了面向对象的功能。
下面开始正式介绍面向对象
二、面向对象编程
类的概念 : 具有相同属性和技能的一类事物
人类就是抽象一个概念
对象 : 就是对一个类的具体的描述
具体的人 ,她有什么特征呢?比如,眉毛弯弯的,眼睛大大的,穿着一件粉色的裙子...
比如说桌子,猫,这些是类的概念,为什么呢?因为它是抽象的。
再比如购物
商品 的大概属性: 名字,类别,价格,产地,保质期,编号...
比如 苹果 生鲜类 5块钱 --- 它是一个对象,因为对它做了具体描述
使用面向对象的好处:
1.使得代码之间的角色关系更加明确
2.增强了代码的可扩展性
3.规范了对象的属性和技能
面向对象的特点:结局的不确定性
新建一个类,类名的首字母最好是大写的,规范一点,否则Pycharm有波浪号
1 2 3 4 | class Person: 静态变量 = 123 print (Person.__dict__) #内置的双下划线方法 |
执行输出:
{'__doc__': None, '静态变量': 123, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__dict__': <attribute '__dict__' of 'Person' objects>}
从结果中,可以找到 '静态变量': 123
访问静态变量,第一种方式
1 | print (Person.__dict__[ '静态变量' ]) |
执行输出:123
测试外部是否可以修改静态变量
1 2 | Person.__dict__[ '静态变量' ] = 456 print (Person.__dict__[ '静态变量' ]) |
执行报错:
TypeError: 'mappingproxy' object does not support item assignment
访问静态变量,第二种方式
1 | print (Person.静态变量) |
执行输出:123
测试外部是否可以修改静态变量
1 2 | Person.静态变量 = 456 print (Person.静态变量) |
执行输出:456
删除静态变量
1 2 | del Person.静态变量 print (Person.__dict__) |
执行输出:456
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
发现找不到 '静态变量': 123 了
总结:
引用静态变量
1.类名.__dict__['静态变量名'] 可以查看,但是不能删改
2.类名.静态变量名 直接就可以访问,可以删改
删除一个静态变量 del 类名.静态变量名
动态变量,指的是函数。为什么呢?因为函数内部要执行一段代码,参数不同,结果是不确定的。
1 2 3 4 5 6 7 | class Person: 静态变量 = 123 #静态属性,静态变量 role = 'person' def f1( self ): #默认带一个参数self,方法,动态属性 print ( 1234567 ) <br> #引用动态变量 Person.f1() |
执行报错:
TypeError: f1() missing 1 required positional argument: 'self'
提示缺少一个参数self
随便传一个参数,再次执行
1 | Person.f1( 1 ) |
执行输出:
1234567
因为self变量必须要传,可不可以不传呢?
可以把self删掉,但是不符合规范
只要是类的方法,必须要传self
self的名字,是约定俗成
总结:
引用动态变量
1.类名.方法名 查看这个方法的内存地址
2.类名.方法名(实参) 调用了这个方法,必须传一个实参,这个实参传给了self
类和对象,是相对的概念
类是已经创造的模子
对象是用模子填充
调用类名加括号,创造一个对象
创造一个命名空间,唯一属于对象
1 2 3 4 | alex = Person() # 创造一个对象 alex 是对象、实例 Person是类 对象 = 类名() |
类变成对象的过程,是实例化的 过程
实例化,是产生实例的过程
总结:
创造一个对象 - 实例化
产生一个实例(对象)的过程
对象 = 类名()
计算机只认识二进制
写的代码,是自己能看懂的。但是执行的过程中,并不是这样种的。
实例化的过程:
1.创造一个实例,将会作为一个实际参数 # python
2.自动触发一个__init__的方法,并且把实例以参数的形式传递给__init__方法中的self形参
3.执行完__init__方法之后,会将self自动返回给alex
__init__方法 :初始化方法,给一个对象添加一些基础属性的方法,一般情况下是针对self的赋值
1 2 3 4 5 6 7 | class Person: role = 'person' #静态属性 def __init__( self ): print ( self ) #查看变量 alex = Person() print (alex) #查看变量 |
执行输出:
<__main__.Person object at 0x0000025F23D8BC18>
<__main__.Person object at 0x0000025F23D8BC18>
可以看到2次查看变量的内存地址是一样的。
也就是说self表示实例本身。
如果实例化时,传一个参数
1 | alex = Person( 'sb' ) |
执行报错:
TypeError: __init__() takes 1 positional argument but 2 were given
因为传的参数是多余的,为什么呢?在没传参之前,执行正常,传参之后,就报错了。
因为实例化时,它把实例本身传给类,self接收了参数,也就是实例本身。再多传一个参数,就报错了。
类里面再多写一个参数,实例化时,传一个参数
1 2 3 4 5 6 7 | class Person: role = 'person' #静态属性 def __init__( self ,name): print ( self ,name) #查看变量 alex = Person( 'sb' ) print (alex) #查看变量 |
执行输出:
<__main__.Person object at 0x00000243EC48B908> sb
<__main__.Person object at 0x00000243EC48B908>
name和self是没有关系的
查看self的值
1 2 3 4 5 6 | class Person: role = 'person' #静态属性 def __init__( self ,name): print ( self .__dict__) #查看变量 alex = Person( 'sb' ) |
执行输出:
{}
self默认有一个空字典
可以给self加参数
1 2 3 4 5 6 7 | class Person: role = 'person' #静态属性 def __init__( self ,name): self .__dict__[ 'name' ] = name alex = Person( 'sb' ) print (alex.__dict__) |
执行输出:
{'name': 'sb'}
类和外部唯一的联系,就是self
让alex拥有自己的字典
1 2 3 4 5 6 7 8 9 10 | class Person: role = 'person' #静态属性 def __init__( self ,name,sex,hp,ad): self .__dict__[ 'name' ] = name self .__dict__[ 'sex' ] = sex self .__dict__[ 'hp' ] = hp self .__dict__[ 'ad' ] = ad alex = Person( 'sb' , 'M' , 1 , 5 ) print (alex.__dict__) |
执行输出:
{'name': 'sb', 'ad': 5, 'sex': 'M', 'hp': 1}
每次调用Person()都会产生一个新的内存空间,它会返回给调用者
但是上面的写法,不规范
第二种写法
1 2 3 4 5 6 7 8 9 10 | class Person: role = 'person' #静态属性 def __init__( self ,name,sex,hp,ad): self .name = name self .sex = sex self .hp = hp self .ad = ad alex = Person( 'sb' , 'M' , 1 , 5 ) print (alex.__dict__) |
执行输出,效果同上。
推荐使用第二种方法,从此以后,就不要使用__dict__的方法修改属性
直接使用对象名.属性名 修改
1 2 3 4 5 6 7 8 9 10 11 | class Person: role = 'person' #静态属性 def __init__( self ,name,sex,hp,ad): self .name = name self .sex = sex self .hp = hp self .ad = ad alex = Person( 'sb' , 'M' , 1 , 5 ) alex.name = 'a_sb' print (alex.name) |
执行输出:
a_sb
属性的调用:
1.对象名.属性名 第一种调用方法,推荐使用
2.对象名.__dict__['属性名'] 第二种调用方
广义上的属性,是指对象的属性
类里面的方法,没有顺序之分
一般把init放到第一个
在类里面的def 一般叫方法
增加一个类方法
1 2 3 4 5 6 7 8 9 | 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 ( '{}发起了一次攻击' . format ( self .name)) |
执行类方法
1 2 | alex = Person( 'sb' , 'M' , 1 , 5 ) Person.attack(alex) |
执行输出:
sb发起了一次攻击
执行类方法可以简写
1 2 | alex = Person( 'sb' , 'M' , 1 , 5 ) alex.attack() |
方法的调用 :
1.类名.方法名(对象名) # 那么方法中的self参数就指向这个对象
2.对象名.方法名() # 这样写 相当于 方法中的self参数直接指向这个对象,推荐使用
attack是和Person关联起来的
所以外部可以直接调用attack方法
今日内容总结:
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 | # 查看静态变量的第一种方式 # print(Person.__dict__) # 内置的双下方法 # print(Person.__dict__['静态变量']) # 查看静态变量的第二种方式 # print(Person.静态变量) # 123 值 # print(Person.role) # Person.静态变量 = 456 # print(Person.静态变量) # del Person.静态变量 # print(Person.__dict__) 类名 # 引用静态变量 # 1.类名.__dict__['静态变量名'] 可以查看,但是不能删改 # 2.类名.静态变量名 直接就可以访问,可以删改 # 删除一个静态变量 del 类名.静态变量名 # 引用动态变量 # 1.类名.方法名 查看这个方法的内存地址 # 1.类名.方法名(实参) 调用了这个方法,必须传一个实参,这个实参传给了self # 创造一个对象 - 实例化 # 产生一个实例(对象)的过程 # 对象 = 类名() # 实例化的过程: # 1.创造一个实例,将会作为一个实际参数 # python # 2.自动触发一个__init__的方法,并且把实例以参数的形式传递给__init__方法中的self形参 # 3.执行完__init__方法之后,会将self自动返回给alex # __init__方法 :初始化方法,给一个对象添加一些基础属性的方法,一般情况下是针对self的赋值<br> # 对象 # 在类的内部 self是本类的一个对象 # 在类的外部,每一个对象都对应着一个名字,这个对象指向一个对象的内存空间 # 属性的调用: # 对象名.属性名 第一种调用方法 # 对象名.__dict__['属性名'] 第二种调用方法 # 方法的调用 : # 类名.方法名(对象名) # 那么方法中的self参数就指向这个对象 # 对象名.方法名() # 这样写 相当于 方法中的self参数直接指向这个对象 |
明天默写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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( '金老板' , '女' , 20 , 50 ) alex.attack() # 相当于执行Person.attack(alex) boss_jin.attack() # 相当于执行Person.attack(boss_jin) |
练习一:在终端输出如下信息
小明,10岁,男,上山去砍柴
小明,10岁,男,开车去东北
小明,10岁,男,最爱大保健
老李,90岁,男,上山去砍柴
老李,90岁,男,开车去东北
老李,90岁,男,最爱大保健
老张…
答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Person( object ): def __init__( self , name, age, sex = '男' , hobby = ( '上山去砍柴' , '开车去东北' , '最爱大保健' )): self .name = name self .age = age self .sex = sex self .hobby = hobby def info( self ): for i in self .hobby: print ( '{},{}岁,{},{}' . format ( self .name, self .age, self .sex, i)) ming = Person( '小明' , 10 ) li = Person( '老李' , 90 ) ming.info() li.info() |
执行输出:
小明,10岁,男,上山去砍柴
小明,10岁,男,开车去东北
小明,10岁,男,最爱大保健
老李,90岁,男,上山去砍柴
老李,90岁,男,开车去东北
老李,90岁,男,最爱大保健
扩展题:
使用面向对象的方式编码三级菜单
将之前的代码复制粘贴过来,切割成面向对象方式
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | # -*- coding: utf-8 -*- class AreaMenu( object ): def __init__( self ): self .zone = { '山东' : { '青岛' : [ '四方' , '黄岛' , '崂山' , '李沧' , '城阳' ], '济南' : [ '历城' , '槐荫' , '高新' , '长青' , '章丘' ], '烟台' : [ '龙口' , '莱山' , '牟平' , '蓬莱' , '招远' ] }, '江苏' : { '苏州' : [ '沧浪' , '相城' , '平江' , '吴中' , '昆山' ], '南京' : [ '白下' , '秦淮' , '浦口' , '栖霞' , '江宁' ], '无锡' : [ '崇安' , '南长' , '北塘' , '锡山' , '江阴' ] }, '浙江' : { '杭州' : [ '西湖' , '江干' , '下城' , '上城' , '滨江' ], '宁波' : [ '海曙' , '江东' , '江北' , '镇海' , '余姚' ], '温州' : [ '鹿城' , '龙湾' , '乐清' , '瑞安' , '永嘉' ] } } self .province = list ( self .zone.keys()) self .run() def run( self ): # 省列表 while True : print ( '省' .center( 20 , '*' )) # 打印省列表 for i in self .province: print ( '{}\t{}' . format ( self .province.index(i) + 1 , i)) province_input = input ( '请输入省编号,或输入q/Q退出:' ).strip() if province_input.isdigit(): province_input = int (province_input) if 0 < province_input < = len ( self .province): # 省编号,由于显示加1,获取的时候,需要减1 province_id = province_input - 1 # 城市列表 city = list ( self .zone[ self .province[province_id]].keys()) # 进入市区列表 self .city(province_id, city) else : print ( "\033[41;1m省编号 {} 不存在!\033[0m" . format (province_input)) elif province_input.upper() = = 'Q' : break else : print ( "\033[41;1m输入省编号非法!\033[0m" ) def city( self , province_id, city): # 市区列表 if province_id = = ' ' or city == ' ': return 'province_id 和 city 参数不能为空' while True : print ( '市' .center( 20 , '*' )) for j in city: print ( '{}\t{}' . format (city.index(j) + 1 , j)) city_input = input ( "请输入市编号,或输入b(back)返回上级菜单,或输入q(quit)退出:" ).strip() if city_input.isdigit(): city_input = int (city_input) if 0 < city_input < = len (city): # 市编号,由于显示加1,获取的时候,需要减1 city_id = city_input - 1 # 县列表 county = self .zone[ self .province[province_id]][city[city_id]] # 进入县列表 self .county(county) else : print ( "\033[41;1m市编号 {} 不存在!\033[0m" . format (city_input)) elif city_input.upper() = = 'B' : break elif city_input.upper() = = 'Q' : # 由于在多层while循环里面,直接exit退出即可 exit() else : print ( "\033[41;1m输入市编号非法!\033[0m" ) def county( self , county): # 县列表 if county = = '': return 'county 参数不能为空' while True : print ( '县' .center( 20 , '*' )) for k in county: print ( '{}\t{}' . format (county.index(k) + 1 , k)) # 到县这一级,不能输入编号了,直接提示返回菜单或者退出 county_input = input ( "输入b(back)返回上级菜单,或输入q(quit)退出:" ).strip() if county_input = = 'b' : # 终止此层while循环,跳转到上一层While break elif county_input = = 'q' : # 结束程序 exit() else : print ( "\033[41;1m已经到底线了,请返回或者退出!\033[0m" ) if __name__ = = '__main__' : AreaMenu() |
执行输出:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· .NET 适配 HarmonyOS 进展
· .NET 进程 stackoverflow异常后,还可以接收 TCP 连接请求吗?
· 本地部署 DeepSeek:小白也能轻松搞定!
· 基于DeepSeek R1 满血版大模型的个人知识库,回答都源自对你专属文件的深度学习。
· 在缓慢中沉淀,在挑战中重生!2024个人总结!
· Tinyfox 简易教程-1:Hello World!
· 大人,时代变了! 赶快把自有业务的本地AI“模型”训练起来!