python 全栈开发,Day17(初识面向对象)

一、引子

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

角色有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)
<br>#引用动态变量
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的赋值<br>
# 对象
    # 在类的内部 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()

执行输出:

posted @ 2020-04-08 10:44  Nidhogg14  阅读(206)  评论(0编辑  收藏  举报