Python笔记 Day15 (面向对象初识)

时间:2021-03-07 19:15:03

一、引子  

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

角色有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表示品种

#创建两个角色
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,为了解决这个问题,需要把攻击函数放在人模子里面,咬人放在狗模子里面,

外部无法直接调用。

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)

 

 


二、面向对象编程

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

    人类就是抽象的一个概念

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

    具体的人,他有什么特征?比如独一无二又帅气的我!!!

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

 

而我家的桌子,我家的猫,就是一个具体的对象!

  

  使用面向对象的好处:

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

    2、增加了代码的可扩展性

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

 

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

在面向对象的过程中,有两种变量:静态变量(数据属性)、动态变量(函数)

1、静态变量

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

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

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

检测外部是否可以修改静态变量
Person.__dict__['静态变量'] = 456   #这种方法是不能修改的
print(Person.__dict__['静态变量'])    # TypeError

#
访问静态变量的第二种方法:
   
print(Person.静态变量)    # 123

#
Person.增加 = 222
print(Person.增)    # 222

#
del Person.静态变量
print(Person.__dict__)

#改,只能用以下
Person.静态变量 = 456

  总结: 引用静态变量

        1、类名.__dirt__['静态变量名']可以查看,但是不能删改

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

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

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  ,self的名字,是约定俗成

  总结:引用动态变量

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

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

 

类和对象,是相对的概念:

  类是已经创造的模子对象是用模子填充

  调用类名加括号,创造一个对象
  创造一个命名空间,唯一属于对象

alex = Person()   # 创造一个对象
alex 是对象、实例
Person是类
对象 = 类名()

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

Python笔记 Day15 (面向对象初识)

  实例化的过程:

    1、创造一个实例,将会作为一个实际参数 

    2、自动触发一个__init__的方法,并且把实际以参数的形式传递给__init__方法中的self形参,这里的self就是实例化的对象

    3、执行完__init__方法之后,会将self自动返回给实例化对象,如上的alex

    4、__init__方法:初始化方法,给一个对象添加一些基本属性的方法,一般情况下针对self赋值,就想类给你了模子,初始化就是让你tian

  写模子,也有点Java的构造函数,注意有了__init__,我们就不用像引子中里的 return self!

class Person:
    role = 'person' #静态属性
    def __init__(self):
        print(self) #查看变量
 
alex = Person()    #会把alex自己传给()里
print(alex) #查看变量

执行输出:
<__main__.Person object at 0x0000025F23D8BC18>
<__main__.Person object at 0x0000025F23D8BC18>
可以看到2次查看变量的内存地址是一样的。
也就是说self表示实例本身。

#如果实例化时,传入一个参数
alex = Person('sb')  # TypeError

因为传的参数是多余的,为什么呢?在没传参之前,执行正常,传参之后,就报错了。
因为实例化时,它把实例本身传给类,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。通过self来区分不同的对象,如图:

Python笔记 Day15 (面向对象初识)

让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__)   # 效果同上

# 修改
alex.name = 'a_sb'
print(alex.name)     # a_sb

#
alex.haha = 'enen'
print(alex.haha)    # enen

#
alex.name = 'nice'

#
del alex.haha
print(Person.__dict__)   # 没有haha属性

 

 属性的调用:
    1.对象名.属性名 第一种调用方法,推荐使用
    2.对象名.__dict__['属性名'] 第二种调用方

增加一个类的方法

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参数直接指向这个对象,推荐使用

 

练习题:

  #上山砍柴实例

class Person:
    def __init__(self,name,age,sex,hobby=('上山去砍柴','开车去东北','最爱大保健')):
        self.mingzi = name
        self.nianling = age
        self.xing_bie = sex
        self.hobby = hobby
    def dong_zuo(self):
         for i in self.hobby:
             print('{},{}岁,{},{}'.format(self.mingzi,self.nianling,self.xing_bie,i))

ming = Person('小明','21','')
ming.dong_zuo()

#输出结果
小明,21岁,男,上山去砍柴
小明,21岁,男,开车去东北
小明,21岁,男,最爱大保健
  #三级菜单实例

class AreaMenu(object): def __init__(self): self.zone = { '山东': { '青岛': ['四方', '黄岛', '崂山', '李沧', '城阳'], '济南': ['历城', '槐荫', '高新', '长青', '章丘'], '烟台': ['龙口', '莱山', '牟平', '蓬莱', '招远'] }, '江苏': { '苏州': ['沧浪', '相城', '平江', '吴中', '昆山'], '南京': ['白下', '秦淮', '浦口', '栖霞', '江宁'], '无锡': ['崇安', '南长', '北塘', '锡山', '江阴'] }, '浙江': { '杭州': ['西湖', '江干', '下城', '上城', '滨江'], '宁波': ['海曙', '江东', '江北', '镇海', '余姚'], '温州': ['鹿城', '龙湾', '乐清', '瑞安', '永嘉'] } } self.province = list(self.zone.keys()) self.run() # 默认直接执行run函数 def run(self): # 省列表 # print('ok') while True: print(''.center(30,'*')) # 分割线 #打印省列表 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) # 因为是在进行自生实例的调用方法,所以必须要self, #否则就要再实例化后 ,再用: 实例名.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(55,"*")) for j in city: print('{}\t{}'.format(city.index(j) + 1, j)) # 注意这里的city已经是传入的了,不能再用self 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 #进入县列表 country = self.zone[self.province[province_id]][city[city_id]] # 注意这里字典的调用 # print(country) #进入县列表 self.country(country) else: print("\033[41;1m市编号 {} 不存在!\033[0m".format(city_input)) elif city_input.upper() =="B": break elif city_input.upper() == 'Q': exit() else: print("\033[41;1m输入市编号非法!\033[0m") def country(self,country): if country == "": return 'country 参数不能为空!' while True: print(''.center(20,'*')) for k in country: print('{}\t{}'.format(country.index(k) + 1, k)) # 到县一级就不能再输入编号了,直接提示返回菜单或则退出 country_input = input("输入b(back)返回上级菜单,或输入q(quit)退出:").strip() if country_input.lower() == 'b': # 终止此层while循环,跳转到上一层While break elif country_input.lower() == 'q': # 结束程序 exit() else: print("\033[41;1m已经到底线了,请返回或者退出!\033[0m") if __name__ == '__main__': AreaMenu()