一、引子

第一次参加工作,进入了一家游戏公司,公司需要开发一款游戏《人狗大战》

一款游戏,首先得把角色和属性定下来。

角色有 2 个,分别是人和狗

属性如下:

人 :昵称、性别、血、攻击力

狗 :名字、品种、血、攻击力

定义 2 个字典

  1. #人
  2. person = {'name': 'xiao_Ming', 'sex':'M', 'hp': 1, 'ad': 5}
  3. #狗
  4. dog = {'name': '旺财', 'sex':'M', 'hp': 100, 'ad': 100}

首先是人攻击狗,定义个函数

  1. def attack(person,dog):
  2. #人攻击狗
  3. print('{}攻击{}'.format(person['name'], dog['name']))
  4. #狗掉血,狗的血量-人的攻击力
  5. dog['hp'] -= person['ad']

执行函数

  1. attack(person,dog)
  2. #查看狗的血量
  3. print(dog['hp'])

执行输出:

xiao_Ming 攻击旺财

95

人攻击了狗,狗得反击吧,再定义一个函数

  1. def bite(dog,person): #狗咬人
  2. print('{}咬了{}'.format(dog['name'], person['name']))
  3. # 人掉血,人的血量-狗的攻击力
  4. person['hp'] -= dog['ad']
  5. #判断人的血量是否小于等于 0
  6. if person['hp'] <= 0:
  7. print('game over,{} win'.format(dog['name']))

执行函数

  1. bite(dog,person)
  2. #查看人的血量
  3. print(person['hp'])

执行输出:

旺财咬了 xiao_Ming

game over,旺财 win

-99

现在还只有一个玩家,有多个玩家怎么办,再加一个?

每添加一个人,就得创建一个字典

但是创造一个人物角色,没有血条,游戏就会有 bug

所以,为了解决这个问题,需要定义一个模板,那么人的属性就固定下来了

定义 2 个函数,人和狗的模板

  1. def Person(name,sex,hp,ad):
  2. # 人模子
  3. self = {'name':name, 'sex':sex, 'hp':hp, 'ad': ad}
  4. return self
  5. def Dog(name,varieties,hp,ad):
  6. # 狗模子
  7. self = {'name': name, 'varieties': varieties, 'hp': hp, 'ad': ad}
  8. return self

注意:self 它不是关键字,只是一个变量而已。varieties 表示品种

创建 2 个角色

  1. person1 = Person('xiao_Ming','M',1,5)
  2. dog1 = Dog('旺财','teddy',100,100)

可以发现,这里就规范了角色的属性个数,简化了创建角色的代码

执行狗咬人函数

  1. bite(dog1,person1)
  2. print(person1['hp'])

执行输出:

旺财咬了 xiao_Ming

game over,旺财 win

-99

如果参数传的顺序乱了,游戏就会有 bug

  1. attack(dog1,person1)
  2. print(person1['hp'])

执行输出:

旺财攻击 xiao_Ming

-199

为了解决这个问题,需要把攻击函数放在人模子里面,咬人放在狗模板里面。

外部无法直接调用。

  1. def Person(name,sex,hp,ad):
  2. # 人模子
  3. self = {'name':name, 'sex':sex, 'hp':hp, 'ad': ad}
  4. def attack(dog): # 人攻击狗
  5. #参数已经被 self 接收了,所以 attack 函数不需要接收 2 个参数,1 个参数就够了
  6. print('{}攻击{}'.format(self['name'], dog['name']))
  7. # 狗掉血,狗的血量-人的攻击力
  8. dog['hp'] -= self['ad']
  9. self['attack'] = attack #增加一个字典 key
  10. print(self) #查看字典的值
  11. return self
  12. def Dog(name,varieties,hp,ad):
  13. # 狗模子
  14. self = {'name': name, 'varieties': varieties, 'hp': hp, 'ad': ad}
  15. def bite(person): # 狗咬人
  16. # 参数已经被 self 接收了,所以 bite 函数不需要接收 2 个参数,1 个参数就够了
  17. print('{}咬了{}'.format(self['name'], person['name']))
  18. # 人掉血,人的血量-狗的攻击力
  19. person['hp'] -= self['ad']
  20. # 判断人的血量是否小于等于 0
  21. if person['hp'] <= 0:
  22. print('game over,{} win'.format(self['name']))
  23. self['bite'] = bite
  24. return self
  25. #创建 2 个角色
  26. person1 = Person('xiao_Ming','M',1,5)
  27. dog1 = Dog('旺财','teddy',100,100)
  28. #执行人攻击狗函数,它是通过字典取值调用的。
  29. person1['attack'](dog1)
  30. print(dog1['hp'])

执行输出:

{‘name’: ‘xiao_Ming’, ‘sex’: ‘M’, ‘hp’: 1, ‘attack’: .attack at 0x000001F56180AAE8>, ‘ad’: 5}

xiao_Ming 攻击旺财

95

字典里面的 attack 对应的值,是一个函数,也就是一个内存地址

把值取出来,传参就可以执行了

person1‘attack’

如果定义多个角色呢?

  1. person1 = Person('xiao_Ming','M',1,5)
  2. person2 = Person('Zhang_san','M',1,5)
  3. person3 = Person('Li_si','M',1,5)

执行输出:

{‘name’: ‘xiao_Ming’, ‘hp’: 1, ‘ad’: 5, ‘sex’: ‘M’, ‘attack’: .attack at 0x000001EC7C3AAAE8>}

{‘name’: ‘Zhang_san’, ‘hp’: 1, ‘ad’: 5, ‘sex’: ‘M’, ‘attack’: .attack at 0x000001EC7C3AAB70>}

{‘name’: ‘Li_si’, ‘hp’: 1, ‘ad’: 5, ‘sex’: ‘M’, ‘attack’: .attack at 0x000001EC7C3AABF8>}

注意观察 attack 的值,它们对应的是不同的内存地址。

函数每执行一次,就会开辟一个新的命名空间,相互之间不受影响。

在命名空间内部,self 是字典,接收参数,attack 是函数,用来调用的。

上面的 2 个函数 Person 和 Dog 用了闭包

这就是用函数的方式完成了面向对象的功能。

下面开始正式介绍面向对象

二、面向对象编程

类的概念 : 具有相同属性和技能的一类事物

人类就是抽象一个概念

对象 : 就是对一个类的具体的描述

具体的人 ,她有什么特征呢?比如,眉毛弯弯的,眼睛大大的,穿着一件粉色的裙子…

比如说桌子,猫,这些是类的概念,为什么呢?因为它是抽象的。

再比如购物

商品 的大概属性: 名字,类别,价格,产地,保质期,编号…

比如 苹果 生鲜类 5 块钱 —- 它是一个对象,因为对它做了具体描述

使用面向对象的好处:

1.使得代码之间的角色关系更加明确

2.增强了代码的可扩展性

3.规范了对象的属性和技能

面向对象的特点:结局的不确定性

新建一个类,类名的首字母最好是大写的,规范一点,否则 Pycharm 有波浪号

  1. class Person:
  2. 静态变量 = 123
  3. print(Person.__dict__) #内置的双下划线方法

执行输出:

{‘doc‘: None, ‘静态变量’: 123, ‘module‘: ‘main‘, ‘weakref‘: , ‘dict‘: }

从结果中,可以找到 ‘静态变量’: 123

访问静态变量,第一种方式

  1. print(Person.__dict__['静态变量'])

执行输出:123

测试外部是否可以修改静态变量

  1. Person.__dict__['静态变量'] = 456
  2. print(Person.__dict__['静态变量'])

执行报错:

TypeError: ‘mappingproxy’ object does not support item assignment

访问静态变量,第二种方式

  1. print(Person.静态变量)

执行输出:123

测试外部是否可以修改静态变量

  1. Person.静态变量 = 456
  2. print(Person.静态变量)

执行输出:456

删除静态变量

  1. del Person.静态变量
  2. print(Person.__dict__)

执行输出:456

{‘module‘: ‘main‘, ‘dict‘: , ‘weakref‘: , ‘doc‘: None}

发现找不到 ‘静态变量’: 123 了

总结:

引用静态变量

1.类名.dict[‘静态变量名’] 可以查看,但是不能删改

2.类名.静态变量名 直接就可以访问,可以删改

删除一个静态变量 del 类名.静态变量名

动态变量,指的是函数。为什么呢?因为函数内部要执行一段代码,参数不同,结果是不确定的。

  1. class Person:
  2. 静态变量 = 123 #静态属性,静态变量
  3. role = 'person'
  4. def f1(self): #默认带一个参数 self,方法,动态属性
  5. print(1234567)
  6. # 引用动态变量
  7. Person.f1()

执行报错:

TypeError: f1() missing 1 required positional argument: ‘self’

提示缺少一个参数 self

随便传一个参数,再次执行

  1. Person.f1(1)

执行输出:

1234567

因为 self 变量必须要传,可不可以不传呢?

可以把 self 删掉,但是不符合规范

只要是类的方法,必须要传 self

self 的名字,是约定俗成

总结:

引用动态变量

1.类名.方法名 查看这个方法的内存地址

2.类名.方法名(实参) 调用了这个方法,必须传一个实参,这个实参传给了 self

类和对象,是相对的概念

类是已经创造的模子

对象是用模子填充

调用类名加括号,创造一个对象

创造一个命名空间,唯一属于对象

  1. alex = Person() # 创造一个对象
  2. alex 是对象、实例
  3. Person 是类
  4. 对象 = 类名()

类变成对象的过程,是实例化的 过程

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597645299754-a3d0ea16-69e0-4843-87a6-b35db358c042.png)

实例化,是产生实例的过程

总结:

创造一个对象 - 实例化

产生一个实例(对象)的过程

对象 = 类名()

计算机只认识二进制

写的代码,是自己能看懂的。但是执行的过程中,并不是这样种的。

实例化的过程:

1.创造一个实例,将会作为一个实际参数 # python

2.自动触发一个init的方法,并且把实例以参数的形式传递给init方法中的 self 形参

3.执行完init方法之后,会将 self 自动返回给 alex

init方法 :初始化方法,给一个对象添加一些基础属性的方法,一般情况下是针对 self 的赋值

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self):
  4. print(self) #查看变量
  5. alex = Person()
  6. 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. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name):
  4. print(self,name) #查看变量
  5. alex = Person('sb')
  6. print(alex) #查看变量

执行输出:

<__main__.Person object at 0x00000243EC48B908> sb

<__main__.Person object at 0x00000243EC48B908>

name 和 self 是没有关系的

查看 self 的值

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name):
  4. print(self.__dict__) #查看变量
  5. alex = Person('sb')

执行输出:

{}

self 默认有一个空字典

可以给 self 加参数

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name):
  4. self.__dict__['name'] = name
  5. alex = Person('sb')
  6. print(alex.__dict__)

执行输出:

{‘name’: ‘sb’}

类和外部唯一的联系,就是 self

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597645299769-57eb802b-ad4c-426b-94b2-5e92e06ff861.png)

让 alex 拥有自己的字典

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name,sex,hp,ad):
  4. self.__dict__['name'] = name
  5. self.__dict__['sex'] = sex
  6. self.__dict__['hp'] = hp
  7. self.__dict__['ad'] = ad
  8. alex = Person('sb','M',1,5)
  9. print(alex.__dict__)

执行输出:

{‘name’: ‘sb’, ‘ad’: 5, ‘sex’: ‘M’, ‘hp’: 1}

每次调用 Person()都会产生一个新的内存空间,它会返回给调用者

但是上面的写法,不规范

第二种写法

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name,sex,hp,ad):
  4. self.name = name
  5. self.sex = sex
  6. self.hp = hp
  7. self.ad = ad
  8. alex = Person('sb','M',1,5)
  9. print(alex.__dict__)

执行输出,效果同上。

推荐使用第二种方法,从此以后,就不要使用dict的方法修改属性

直接使用对象名.属性名 修改

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name,sex,hp,ad):
  4. self.name = name
  5. self.sex = sex
  6. self.hp = hp
  7. self.ad = ad
  8. alex = Person('sb','M',1,5)
  9. alex.name = 'a_sb'
  10. print(alex.name)

执行输出:

a_sb

属性的调用:

1.对象名.属性名 第一种调用方法,推荐使用

2.对象名.dict[‘属性名’] 第二种调用方

广义上的属性,是指对象的属性

类里面的方法,没有顺序之分

一般把 init 放到第一个

在类里面的 def 一般叫方法

增加一个类方法

  1. class Person:
  2. role = 'person' #静态属性
  3. def __init__(self,name,sex,hp,ad):
  4. self.name = name
  5. self.sex = sex
  6. self.hp = hp
  7. self.ad = ad
  8. def attack(self):
  9. print('{}发起了一次攻击'.format(self.name))

执行类方法

  1. alex = Person('sb','M',1,5)
  2. Person.attack(alex)

执行输出:

sb 发起了一次攻击

执行类方法可以简写

  1. alex = Person('sb','M',1,5)
  2. alex.attack()

方法的调用 :

1.类名.方法名(对象名) # 那么方法中的 self 参数就指向这个对象

2.对象名.方法名() # 这样写 相当于 方法中的 self 参数直接指向这个对象,推荐使用

attack 是和 Person 关联起来的

所以外部可以直接调用 attack 方法

今日内容总结:

查看静态变量的第一种方式

  1. print(Person.__dict__) # 内置的双下方法
  2. print(Person.__dict__['静态变量'])

查看静态变量的第二种方式

  1. print(Person.静态变量) # 123 值
  2. print(Person.role)
  3. Person.静态变量 = 456
  4. print(Person.静态变量)
  5. del Person.静态变量
  6. 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 参数直接指

明天默写:

  1. class Person:
  2. role = 'person' # 静态属性
  3. def __init__(self,name,sex,hp,ad):
  4. self.name = name # 对象属性 属性
  5. self.sex = sex
  6. self.hp = hp
  7. self.ad = ad
  8. def attack(self):
  9. print('%s 发起了一次攻击'%self.name)
  10. alex = Person('a_sb','不详',1,5)
  11. boss_jin = Person('金老板','女',20,50)
  12. alex.attack() # 相当于执行 Person.attack(alex)
  13. boss_jin.attack() # 相当于执行 Person.attack(boss_jin)

练习一:在终端输出如下信息

小明,10 岁,男,上山去砍柴

小明,10 岁,男,开车去东北

小明,10 岁,男,最爱大保健

老李,90 岁,男,上山去砍柴

老李,90 岁,男,开车去东北

老李,90 岁,男,最爱大保健

老张…

答案:

  1. class Person(object):
  2. def __init__(self, name, age, sex='男', hobby=('上山去砍柴', '开车去东北', '最爱大保健')):
  3. self.name = name
  4. self.age = age
  5. self.sex = sex
  6. self.hobby = hobby
  7. def info(self):
  8. for i in self.hobby:
  9. print('{},{}岁,{},{}'.format(self.name, self.age, self.sex, i))
  10. ming = Person('小明', 10)
  11. li = Person('老李', 90)
  12. ming.info()
  13. li.info()

执行输出:

小明,10 岁,男,上山去砍柴

小明,10 岁,男,开车去东北

小明,10 岁,男,最爱大保健

老李,90 岁,男,上山去砍柴

老李,90 岁,男,开车去东北

老李,90 岁,男,最爱大保健

扩展题:

使用面向对象的方式编码三级菜单

将之前的代码复制粘贴过来,切割成面向对象方式

  1. # -*- coding: utf-8 -*-
  2. class AreaMenu(object):
  3. def __init__(self):
  4. self.zone = {
  5. '山东': {
  6. '青岛': ['四方', '黄岛', '崂山', '李沧', '城阳'],
  7. '济南': ['历城', '槐荫', '高新', '长青', '章丘'],
  8. '烟台': ['龙口', '莱山', '牟平', '蓬莱', '招远']
  9. },
  10. '江苏': {
  11. '苏州': ['沧浪', '相城', '平江', '吴中', '昆山'],
  12. '南京': ['白下', '秦淮', '浦口', '栖霞', '江宁'],
  13. '无锡': ['崇安', '南长', '北塘', '锡山', '江阴']
  14. },
  15. '浙江': {
  16. '杭州': ['西湖', '江干', '下城', '上城', '滨江'],
  17. '宁波': ['海曙', '江东', '江北', '镇海', '余姚'],
  18. '温州': ['鹿城', '龙湾', '乐清', '瑞安', '永嘉']
  19. }
  20. }
  21. self.province = list(self.zone.keys())
  22. self.run()
  23. def run(self): # 省列表
  24. while True:
  25. print('省'.center(20, '*'))
  26. # 打印省列表
  27. for i in self.province:
  28. print('{}\t{}'.format(self.province.index(i) + 1, i))
  29. province_input = input('请输入省编号,或输入 q/Q 退出:').strip()
  30. if province_input.isdigit():
  31. province_input = int(province_input)
  32. if 0 < province_input <= len(self.province):
  33. # 省编号,由于显示加 1,获取的时候,需要减 1
  34. province_id = province_input - 1
  35. # 城市列表
  36. city = list(self.zone[self.province[province_id]].keys())
  37. # 进入市区列表
  38. self.city(province_id, city)
  39. else:
  40. print("\033[41;1m 省编号 {} 不存在!\033[0m".format(province_input))
  41. elif province_input.upper() == 'Q':
  42. break
  43. else:
  44. print("\033[41;1m 输入省编号非法!\033[0m")
  45. def city(self, province_id, city): # 市区列表
  46. if province_id == '' or city == '':
  47. return 'province_id 和 city 参数不能为空'
  48. while True:
  49. print('市'.center(20, '*'))
  50. for j in city:
  51. print('{}\t{}'.format(city.index(j) + 1, j))
  52. city_input = input("请输入市编号,或输入 b(back)返回上级菜单,或输入 q(quit)退出:").strip()
  53. if city_input.isdigit():
  54. city_input = int(city_input)
  55. if 0 < city_input <= len(city):
  56. # 市编号,由于显示加 1,获取的时候,需要减 1
  57. city_id = city_input - 1
  58. # 县列表
  59. county = self.zone[self.province[province_id]][city[city_id]]
  60. # 进入县列表
  61. self.county(county)
  62. else:
  63. print("\033[41;1m 市编号 {} 不存在!\033[0m".format(city_input))
  64. elif city_input.upper() == 'B':
  65. break
  66. elif city_input.upper() == 'Q':
  67. # 由于在多层 while 循环里面,直接 exit 退出即可
  68. exit()
  69. else:
  70. print("\033[41;1m 输入市编号非法!\033[0m")
  71. def county(self, county): # 县列表
  72. if county == '':
  73. return 'county 参数不能为空'
  74. while True:
  75. print('县'.center(20, '*'))
  76. for k in county:
  77. print('{}\t{}'.format(county.index(k) + 1, k))
  78. # 到县这一级,不能输入编号了,直接提示返回菜单或者退出
  79. county_input = input("输入 b(back)返回上级菜单,或输入 q(quit)退出:").strip()
  80. if county_input == 'b':
  81. # 终止此层 while 循环,跳转到上一层 While
  82. break
  83. elif county_input == 'q':
  84. # 结束程序
  85. exit()
  86. else:
  87. print("\033[41;1m 已经到底线了,请返回或者退出!\033[0m")
  88. if __name__ == '__main__':
  89. AreaMenu()

执行输出:

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597645299705-923141c7-7e1f-430d-be1d-fa582b92e4f5.png)