一、引子

第一次参加工作,进入了一家游戏公司,公司需要开发一款游戏《人狗大战》
一款游戏,首先得把角色和属性定下来。

角色有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']
#判断人的血量是否小于等于0
if 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 self def 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 #增加一个字典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)

如果定义多个角色呢?

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有波浪号

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

执行输出:

{'__doc__': None, '静态变量': 123, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__dict__': <attribute '__dict__' of 'Person' objects>}

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

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

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

执行输出:123

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

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

执行报错:

TypeError: 'mappingproxy' object does not support item assignment

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

print(Person.静态变量)

执行输出:123

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

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

执行输出:456

删除静态变量

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 类名.静态变量名

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

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'] = name alex = Person('sb')
print(alex.__dict__)

执行输出:

{'name': 'sb'}

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

让alex拥有自己的字典

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()都会产生一个新的内存空间,它会返回给调用者
但是上面的写法,不规范

第二种写法

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__的方法修改属性

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

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 一般叫方法

增加一个类方法

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))

执行类方法

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.静态变量 = 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的赋值
# 对象
# 在类的内部 self是本类的一个对象
# 在类的外部,每一个对象都对应着一个名字,这个对象指向一个对象的内存空间
# 属性的调用:
# 对象名.属性名 第一种调用方法
# 对象名.__dict__['属性名'] 第二种调用方法
# 方法的调用 :
# 类名.方法名(对象名) # 那么方法中的self参数就指向这个对象
# 对象名.方法名() # 这样写 相当于 方法中的self参数直接指向这个对象

明天默写:

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岁,男,最爱大保健
老张…

答案:

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岁,男,最爱大保健

 扩展题:

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

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

# -*- 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()

执行输出:

python 全栈开发,Day17(初识面向对象)的更多相关文章

  1. Python全栈开发:初识Python

    Pythton简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语 ...

  2. 【python全栈开发】初识python

    本人最开始接触python是在2013年接触,写过hello word!在此之前对开发类没有多大兴趣,不知道重要性,属于浑浑噩噩,忙忙乎乎,跌跌撞撞的.随后选择了Linux运维作为就业主攻方向. 经过 ...

  3. 巨蟒python全栈开发django2:初识django

    今日内容大纲: 1.起飞版web框架 2.自定制框架的流程 3.jinja2模板渲染初识 4.MVC&&MTV 5.django版本介绍及django安装 6.django初识(一些操 ...

  4. python全栈开发从入门到放弃之迭代器生成器

    1.python中的for循环 l = [1,2,3,4,5,6] for i in l: #根据索引取值 print(i) 输出结果: 1 2 3 4 5 6 2.iterable  可迭代的 可迭 ...

  5. Python全栈开发【面向对象进阶】

    Python全栈开发[面向对象进阶] 本节内容: isinstance(obj,cls)和issubclass(sub,super) 反射 __setattr__,__delattr__,__geta ...

  6. Python全栈开发【面向对象】

    Python全栈开发[面向对象] 本节内容: 三大编程范式 面向对象设计与面向对象编程 类和对象 静态属性.类方法.静态方法 类组合 继承 多态 封装 三大编程范式 三大编程范式: 1.面向过程编程 ...

  7. python 全栈开发,Day99(作业讲解,DRF版本,DRF分页,DRF序列化进阶)

    昨日内容回顾 1. 为什么要做前后端分离? - 前后端交给不同的人来编写,职责划分明确. - API (IOS,安卓,PC,微信小程序...) - vue.js等框架编写前端时,会比之前写jQuery ...

  8. 学习笔记之Python全栈开发/人工智能公开课_腾讯课堂

    Python全栈开发/人工智能公开课_腾讯课堂 https://ke.qq.com/course/190378 https://github.com/haoran119/ke.qq.com.pytho ...

  9. Python全栈开发相关课程

    Python全栈开发 Python入门 Python安装 Pycharm安装.激活.使用 Python基础 Python语法 Python数据类型 Python进阶 面向对象 网络编程 并发编程 数据 ...

  10. Python 全栈开发【第0篇】:目录

    Python 全栈开发[第0篇]:目录   第一阶段:Python 开发入门 Python 全栈开发[第一篇]:计算机原理&Linux系统入门 Python 全栈开发[第二篇]:Python基 ...

随机推荐

  1. elementUI 表格设置表头样式

    eader-row-class-name 表头行的 className 的回调方法,也可以使用字符串为所有表头行设置一个固定的 className. Function({row, rowIndex}) ...

  2. 如何写一个好的缺陷(Defect)报告

    编写缺陷报告是测试人员的日常工作,好的缺陷报告能够让开发人员更容易理解,更快速的定位问题:不好的缺陷报告可能会误导调查方向,增加沟通成本.那么一个好的缺陷报告应该包括哪些方面呢? 请看我的mindma ...

  3. OpenCV入门(1)- 简介

    1.图像的表示 在计算机看来,图像只是一些亮度各异的点,一副M*N的图片可以用M*N的矩阵来表示,矩阵的值表示这个位置上像素的亮度. 一般灰度图用二维矩阵来表示,彩色(多通道)图用三维矩阵表示,大部分 ...

  4. pytorch中如何使用DataLoader对数据集进行批处理

    最近搞了搞minist手写数据集的神经网络搭建,一个数据集里面很多个数据,不能一次喂入,所以需要分成一小块一小块喂入搭建好的网络. pytorch中有很方便的dataloader函数来方便我们进行批处 ...

  5. Biorhythms HDU - 1370 (中国剩余定理)

    孙子定理: 当前存在三个式子,t%3=2,t%5=3,t%7=2.然后让你求出t的值的一个通解. 具体过程:选取3和5的一个公倍数t1能够使得这个公倍数t1%7==1,然后选取3和7的一个公倍数t2使 ...

  6. 基于神经网络的颜色恒常性—Fully Convolutional Color Constancy with Confidence-weighted Pooling

    论文地址: http://openaccess.thecvf.com/content_cvpr_2017/papers/Hu_FC4_Fully_Convolutional_CVPR_2017_pap ...

  7. RoIPooling、RoIAlign笔记

    一).RoIPooling 这个可以在Faster RCNN中使用以便使生成的候选框region proposal映射产生固定大小的feature map 先贴出一张图,接着通过这图解释RoiPool ...

  8. 一套oracle的练习题

    create table student( sno varchar2(10) primary key, sname varchar2(20), sage number(2), ssex varchar ...

  9. Latex 公式居中

    这么简单的功能要是还要加工具包,LaTeX也不用混了~ 公式用\[...\]来写可以达到公式居中效果. 或者在equation环境外加center环境: \begin{center} \begin{e ...

  10. 【vim】删除指定标记前的内容 dt[标记]

    和删除标记内部有些相似,但目的不同.命令如下: dt[标记] 会删除所有光标和标记之间的内容(保持标记不动),如果在同一行有这个标记的话.例如 dt. 会删除至句子的末尾,但保持 '.' 不动.