一、引子
第一次参加工作,进入了一家游戏公司,公司需要开发一款游戏《人狗大战》
一款游戏,首先得把角色和属性定下来。
角色有 2 个,分别是人和狗
属性如下:
人 :昵称、性别、血、攻击力
狗 :名字、品种、血、攻击力
定义 2 个字典
#人person = {'name': 'xiao_Ming', 'sex':'M', 'hp': 1, 'ad': 5}#狗dog = {'name': '旺财', 'sex':'M', 'hp': 100, 'ad': 100}
首先是人攻击狗,定义个函数
def attack(person,dog):#人攻击狗print('{}攻击{}'.format(person['name'], dog['name']))#狗掉血,狗的血量-人的攻击力dog['hp'] -= person['ad']
执行函数
attack(person,dog)#查看狗的血量print(dog['hp'])
执行输出:
xiao_Ming 攻击旺财
95
人攻击了狗,狗得反击吧,再定义一个函数
def bite(dog,person): #狗咬人print('{}咬了{}'.format(dog['name'], person['name']))# 人掉血,人的血量-狗的攻击力person['hp'] -= dog['ad']#判断人的血量是否小于等于 0if person['hp'] <= 0:print('game over,{} win'.format(dog['name']))
执行函数
bite(dog,person)#查看人的血量print(person['hp'])
执行输出:
旺财咬了 xiao_Ming
game over,旺财 win
-99
现在还只有一个玩家,有多个玩家怎么办,再加一个?
每添加一个人,就得创建一个字典
但是创造一个人物角色,没有血条,游戏就会有 bug
所以,为了解决这个问题,需要定义一个模板,那么人的属性就固定下来了
定义 2 个函数,人和狗的模板
def Person(name,sex,hp,ad):# 人模子self = {'name':name, 'sex':sex, 'hp':hp, 'ad': ad}return selfdef Dog(name,varieties,hp,ad):# 狗模子self = {'name': name, 'varieties': varieties, 'hp': hp, 'ad': ad}return self
注意:self 它不是关键字,只是一个变量而已。varieties 表示品种
创建 2 个角色
person1 = Person('xiao_Ming','M',1,5)dog1 = Dog('旺财','teddy',100,100)
可以发现,这里就规范了角色的属性个数,简化了创建角色的代码
执行狗咬人函数
bite(dog1,person1)print(person1['hp'])
执行输出:
旺财咬了 xiao_Ming
game over,旺财 win
-99
如果参数传的顺序乱了,游戏就会有 bug
attack(dog1,person1)print(person1['hp'])
执行输出:
旺财攻击 xiao_Ming
-199
为了解决这个问题,需要把攻击函数放在人模子里面,咬人放在狗模板里面。
外部无法直接调用。
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 #增加一个字典 keyprint(self) #查看字典的值return selfdef 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']# 判断人的血量是否小于等于 0if person['hp'] <= 0:print('game over,{} win'.format(self['name']))self['bite'] = bitereturn 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’:
xiao_Ming 攻击旺财
95
字典里面的 attack 对应的值,是一个函数,也就是一个内存地址
把值取出来,传参就可以执行了
person1‘attack’
如果定义多个角色呢?
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’:
{‘name’: ‘Zhang_san’, ‘hp’: 1, ‘ad’: 5, ‘sex’: ‘M’, ‘attack’:
{‘name’: ‘Li_si’, ‘hp’: 1, ‘ad’: 5, ‘sex’: ‘M’, ‘attack’:
注意观察 attack 的值,它们对应的是不同的内存地址。
函数每执行一次,就会开辟一个新的命名空间,相互之间不受影响。
在命名空间内部,self 是字典,接收参数,attack 是函数,用来调用的。
上面的 2 个函数 Person 和 Dog 用了闭包
这就是用函数的方式完成了面向对象的功能。
下面开始正式介绍面向对象
二、面向对象编程
类的概念 : 具有相同属性和技能的一类事物
人类就是抽象一个概念
对象 : 就是对一个类的具体的描述
具体的人 ,她有什么特征呢?比如,眉毛弯弯的,眼睛大大的,穿着一件粉色的裙子…
比如说桌子,猫,这些是类的概念,为什么呢?因为它是抽象的。
再比如购物
商品 的大概属性: 名字,类别,价格,产地,保质期,编号…
比如 苹果 生鲜类 5 块钱 —- 它是一个对象,因为对它做了具体描述
使用面向对象的好处:
1.使得代码之间的角色关系更加明确
2.增强了代码的可扩展性
3.规范了对象的属性和技能
面向对象的特点:结局的不确定性
新建一个类,类名的首字母最好是大写的,规范一点,否则 Pycharm 有波浪号
class Person:静态变量 = 123print(Person.__dict__) #内置的双下划线方法
执行输出:
{‘doc‘: None, ‘静态变量’: 123, ‘module‘: ‘main‘, ‘weakref‘:
从结果中,可以找到 ‘静态变量’: 123
访问静态变量,第一种方式
print(Person.__dict__['静态变量'])
执行输出:123
测试外部是否可以修改静态变量
Person.__dict__['静态变量'] = 456print(Person.__dict__['静态变量'])
执行报错:
TypeError: ‘mappingproxy’ object does not support item assignment
访问静态变量,第二种方式
print(Person.静态变量)
执行输出:123
测试外部是否可以修改静态变量
Person.静态变量 = 456print(Person.静态变量)
执行输出:456
删除静态变量
del Person.静态变量print(Person.__dict__)
执行输出:456
{‘module‘: ‘main‘, ‘dict‘:
发现找不到 ‘静态变量’: 123 了
总结:
引用静态变量
1.类名.dict[‘静态变量名’] 可以查看,但是不能删改
2.类名.静态变量名 直接就可以访问,可以删改
删除一个静态变量 del 类名.静态变量名
动态变量,指的是函数。为什么呢?因为函数内部要执行一段代码,参数不同,结果是不确定的。
class Person:静态变量 = 123 #静态属性,静态变量role = 'person'def f1(self): #默认带一个参数 self,方法,动态属性print(1234567)# 引用动态变量Person.f1()
执行报错:
TypeError: f1() missing 1 required positional argument: ‘self’
提示缺少一个参数 self
随便传一个参数,再次执行
Person.f1(1)
执行输出:
1234567
因为 self 变量必须要传,可不可以不传呢?
可以把 self 删掉,但是不符合规范
只要是类的方法,必须要传 self
self 的名字,是约定俗成
总结:
引用动态变量
1.类名.方法名 查看这个方法的内存地址
2.类名.方法名(实参) 调用了这个方法,必须传一个实参,这个实参传给了 self
类和对象,是相对的概念
类是已经创造的模子
对象是用模子填充
调用类名加括号,创造一个对象
创造一个命名空间,唯一属于对象
alex = Person() # 创造一个对象alex 是对象、实例Person 是类对象 = 类名()
类变成对象的过程,是实例化的 过程

实例化,是产生实例的过程
总结:
创造一个对象 - 实例化
产生一个实例(对象)的过程
对象 = 类名()
计算机只认识二进制
写的代码,是自己能看懂的。但是执行的过程中,并不是这样种的。
实例化的过程:
1.创造一个实例,将会作为一个实际参数 # python
2.自动触发一个init的方法,并且把实例以参数的形式传递给init方法中的 self 形参
3.执行完init方法之后,会将 self 自动返回给 alex
init方法 :初始化方法,给一个对象添加一些基础属性的方法,一般情况下是针对 self 的赋值
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 表示实例本身。
如果实例化时,传一个参数
alex = Person('sb')
执行报错:
TypeError: init() takes 1 positional argument but 2 were given
因为传的参数是多余的,为什么呢?在没传参之前,执行正常,传参之后,就报错了。
因为实例化时,它把实例本身传给类,self 接收了参数,也就是实例本身。再多传一个参数,就报错了。
类里面再多写一个参数,实例化时,传一个参数
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 的值
class Person:role = 'person' #静态属性def __init__(self,name):print(self.__dict__) #查看变量alex = Person('sb')
执行输出:
{}
self 默认有一个空字典
可以给 self 加参数
class Person:role = 'person' #静态属性def __init__(self,name):self.__dict__['name'] = namealex = Person('sb')print(alex.__dict__)
执行输出:
{‘name’: ‘sb’}
类和外部唯一的联系,就是 self

让 alex 拥有自己的字典
class Person:role = 'person' #静态属性def __init__(self,name,sex,hp,ad):self.__dict__['name'] = nameself.__dict__['sex'] = sexself.__dict__['hp'] = hpself.__dict__['ad'] = adalex = Person('sb','M',1,5)print(alex.__dict__)
执行输出:
{‘name’: ‘sb’, ‘ad’: 5, ‘sex’: ‘M’, ‘hp’: 1}
每次调用 Person()都会产生一个新的内存空间,它会返回给调用者
但是上面的写法,不规范
第二种写法
class Person:role = 'person' #静态属性def __init__(self,name,sex,hp,ad):self.name = nameself.sex = sexself.hp = hpself.ad = adalex = Person('sb','M',1,5)print(alex.__dict__)
执行输出,效果同上。
推荐使用第二种方法,从此以后,就不要使用dict的方法修改属性
直接使用对象名.属性名 修改
class Person:role = 'person' #静态属性def __init__(self,name,sex,hp,ad):self.name = nameself.sex = sexself.hp = hpself.ad = adalex = Person('sb','M',1,5)alex.name = 'a_sb'print(alex.name)
执行输出:
a_sb
属性的调用:
1.对象名.属性名 第一种调用方法,推荐使用
2.对象名.dict[‘属性名’] 第二种调用方
广义上的属性,是指对象的属性
类里面的方法,没有顺序之分
一般把 init 放到第一个
在类里面的 def 一般叫方法
增加一个类方法
class Person:role = 'person' #静态属性def __init__(self,name,sex,hp,ad):self.name = nameself.sex = sexself.hp = hpself.ad = addef attack(self):print('{}发起了一次攻击'.format(self.name))
执行类方法
alex = Person('sb','M',1,5)Person.attack(alex)
执行输出:
sb 发起了一次攻击
执行类方法可以简写
alex = Person('sb','M',1,5)alex.attack()
方法的调用 :
1.类名.方法名(对象名) # 那么方法中的 self 参数就指向这个对象
2.对象名.方法名() # 这样写 相当于 方法中的 self 参数直接指向这个对象,推荐使用
attack 是和 Person 关联起来的
所以外部可以直接调用 attack 方法
今日内容总结:
查看静态变量的第一种方式
print(Person.__dict__) # 内置的双下方法print(Person.__dict__['静态变量'])
查看静态变量的第二种方式
print(Person.静态变量) # 123 值print(Person.role)Person.静态变量 = 456print(Person.静态变量)del Person.静态变量print(Person.__dict__)
类名
引用静态变量
1.类名.dict[‘静态变量名’] 可以查看,但是不能删改
2.类名.静态变量名 直接就可以访问,可以删改
删除一个静态变量 del 类名.静态变量名
引用动态变量
1.类名.方法名 查看这个方法的内存地址
2.类名.方法名(实参) 调用了这个方法,必须传一个实参,这个实参传给了 self
创造一个对象 - 实例化
产生一个实例(对象)的过程
对象 = 类名()
实例化的过程:
1.创造一个实例,将会作为一个实际参数 # python
2.自动触发一个init的方法,并且把实例以参数的形式传递给init方法中的 self 形参
3.执行完init方法之后,会将 self 自动返回给 alex
init方法 :
初始化方法,给一个对象添加一些基础属性的方法,一般情况下是针对 self 的赋值
对象
在类的内部 self 是本类的一个对象
在类的外部,每一个对象都对应着一个名字,这个对象指向一个对象的内存空间
属性的调用:
对象名.属性名 第一种调用方法
对象名.dict[‘属性名’] 第二种调用方法
方法的调用 :
类名.方法名(对象名) # 那么方法中的 self 参数就指向这个对象
对象名.方法名() # 这样写 相当于 方法中的 self 参数直接指
明天默写:
class Person:role = 'person' # 静态属性def __init__(self,name,sex,hp,ad):self.name = name # 对象属性 属性self.sex = sexself.hp = hpself.ad = addef 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 岁,男,最爱大保健
老张…
答案:
class Person(object):def __init__(self, name, age, sex='男', hobby=('上山去砍柴', '开车去东北', '最爱大保健')):self.name = nameself.age = ageself.sex = sexself.hobby = hobbydef 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 岁,男,最爱大保健
扩展题:
使用面向对象的方式编码三级菜单
将之前的代码复制粘贴过来,切割成面向对象方式
# -*- 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,获取的时候,需要减 1province_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':breakelse: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,获取的时候,需要减 1city_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':breakelif 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 循环,跳转到上一层 Whilebreakelif county_input == 'q':# 结束程序exit()else:print("\033[41;1m 已经到底线了,请返回或者退出!\033[0m")if __name__ == '__main__':AreaMenu()
执行输出:

