面向对象前戏
面向对象的理念如若用文字概念生搬硬套,不免有些晦涩难懂,所以我们采用代码编写人狗大战的方式来深入理解面向对象。
人狗大战
首先创造出人和狗
1.'''推导步骤1:用字典模拟人和狗'''
person1 = {
'name': 'jason',
'p_type': '猛男',
'attack_val': 10,
'life_val': 1000
}
person2 = {
'name': 'kevin',
'p_type': '闷骚男',
'attack_val': 20,
'life_val': 800
}
dog1 = {
'name': '小黑',
'd_type': '泰迪',
'attack_val': 40,
'life_val': 600
}
dog2 = {
'name': '小黄',
'd_type': '秋田',
'attack_val': 30,
'life_val': 600
}
# 由于我们定义人和狗的字典基本不变,而这些字典我们每次新定义一个人或狗都要重新写一份代码,(同一份代码,多个地方多次使用)非常的麻烦,所以我们考虑用函数来封装创建人和狗的代码
2.'''推导步骤2:用函数来封装创建人和狗的代码'''
def get_person(name, p_type, attack_val, life_val):
person_obj = {
'name': name,
'p_type': p_type,
'attack_val': attack_val,
'life_val': life_val
}
return person_obj
def get_dog(name, d_type, attack_val, life_val):
dog_obj = {
'name': name,
'd_type': d_type,
'attack_val': attack_val,
'life_val': life_val
}
return dog_obj
这样,我们每次调用get_person,都能创建出一个人或狗
person1 = get_person('jason', '猛男', 10, 1000)
dog1 = get_dog('小黑', '秋田', 20, 300)
3.'''推导步骤3:给予人和狗攻击对方的能力,其实就是定义两个攻击的函数供两者调用'''
def person_attack(person, dog):
# 提前展示被打的狗的状态
print(f'即将被打的狗:{dog["name"]}, 当前血量:{dog["life_val"]}')
# 最简单暴力的攻击手段:狗的血量 - 人的攻击力
dog['life_val'] -= person['attack_val']
# 展示被打后的状态
print(f'狗{dog["name"]}被人{person["name"]}打了一拳,掉血{person["attack_val"]},当前血量:{dog["life_val"]}')
def dog_attack(dog, person):
# 提前展示被打的人的状态
print(f'即将被打的人:{person["name"]}, 当前血量:{person["life_val"]}')
# 最简单暴力的攻击手段:人的血量 - 狗的攻击力
person['life_val'] -= dog['attack_val']
# 展示被打后的状态
print(f'人{person["name"]}被狗{dog["name"]}咬了一口,掉血{dog["attack_val"]},当前血量:{person["life_val"]}')
# 开始互相攻击
person_attack(person1, dog1)
# 即将被打的狗:小黑, 当前血量:300
# 狗小黑被人jason打了一拳,掉血10,当前血量:290
dog_attack(dog1, person1)
# 即将被咬的人:jason, 当前血量:1000
# 人jason被狗小黑咬了一口,掉血20,当前血量:980
至此,最初版本的人狗大战已经告一段落,接下来是优化版本。
代码优化
问题分析:
人和狗的攻击函数可以被任意调用:狗能调用人的攻击函数,人能使用狗的攻击函数。
person_attack(dog1, person1)
# 即将被打的狗:jason, 当前血量:980
# 狗jason被人小黑打了一拳,掉血20,当前血量:960
可以看到人和狗的攻击乱了套,那么我们能不能让人只能调用人攻击的函数,狗只能调用狗攻击的函数呢?
'''推导步骤4:人跟人的攻击函数绑定,狗跟狗的攻击函数绑定。'''
我们定义的函数默认情况下都是可以被任意调用的 但是现在我们想实现定义的函数只有特定的东西才可以调用:把攻击函数定义在 get_person局部名称空间里
def get_person(name, p_type, attack_val, life_val):
def person_attack(person, dog):
# 提前展示被打的狗的状态
print(f'即将被打的狗:{dog["name"]}, 当前血量:{dog["life_val"]}')
# 最简单暴力的攻击手段:狗的血量 - 人的攻击力
dog['life_val'] -= person['attack_val']
# 展示被打后的状态
print(f'狗{dog["name"]}被人{person["name"]}打了一拳,掉血{person["attack_val"]},当前血量:{dog["life_val"]}')
person_obj = {
'name': name,
'p_type': p_type,
'attack_val': attack_val,
'life_val': life_val
}
return person_obj
def get_dog(name, d_type, attack_val, life_val):
def dog_attack(dog, person):
# 提前展示被打的人的状态
print(f'即将被打的人:{person["name"]}, 当前血量:{person["life_val"]}')
# 最简单暴力的攻击手段:人的血量 - 狗的攻击力
person['life_val'] -= dog['attack_val']
# 展示被打后的状态
print(f'人{person["name"]}被狗{dog["name"]}咬了一口,掉血{dog["attack_val"]},当前血量:{person["life_val"]}')
dog_obj = {
'name': name,
'd_type': d_type,
'attack_val': attack_val,
'life_val': life_val
}
return dog_obj
# 如此我们再想用狗去调用人的攻击函数就万万不能了,经过以上步骤我们就将人的数据(人的信息字典)和人的攻击函数(功能)绑定到了一起,其实
'将功能和数据绑定到一起的操作就是面向对象编程的一种思想体现。'
总结
将人的数据跟人的功能绑定到一起 只有人可以调用人的功能.
将狗的数据跟狗的功能绑定到一起 只有狗可以调用狗的功能.
我们将数据与功能绑定到一起的操作起名为:'面向对象编程' 。
本质:将特定的数据与特定的功能绑定到一起 将来只能彼此相互使用
编程思想的转变
1.面向过程编程
截止昨天 我们所编写的代码都是面向过程编程
过程其实就是流程 面向过程编程其实就是在执行一系列的流程
eg: 注册功能 登录功能 冻结账户 ...
就是按照指定的步骤依次执行 最终就可以得到想要的结果
2.面向对象编程
核心就是'对象'二字
对象就是一个容器,里边存放的是这个对象的数据和功能。
例如:游戏里,我们创造出后羿这个英雄,我们只负责让这个英雄拥有它独有的数据和技能,至于它的战绩如何,我们程序员不用管。
'''
面向过程编程相当于让你给出一个问题的具体解决方案
面向对象编程相当于让你创造出一些事物之后不用你管
'''
上述两种编程思想没有优劣之分 仅仅是使用场景不同 甚至很多时候是两者混合使用
对象与类的概念
对象:数据与功能的结合体
类:多个对象相同的数据个功能的结合体
或者说:'类是对象的抽象,对象是类的实体。'
类主要用于记录多个对象相同的数据和功能
对象则用于记录多个对象不同的数据和功能
在面向对象编程中,类仅仅用于节省代码,对象才是核心。
对象与类的创建
在现实生活中理论是应该先有一个个的个体(对象)再有一个个的群体(类)
在编程世界中必须要先有类才能产生对象
创建类的语法
class 类名:
对象公共的数据
对象公共的方法
1. class 是定义类的关键字
2. 类名与变量名的命名规则一致,并且推荐首字母大写(方便与函数名区分)
3. 与函数不同的是:类体代码会在类的定义阶段就执行
class Student:
school: '清华大学'
def run(self):
print('student run')
1.查看类的名称空间的方法
print(Student.__dict__) # 使用该方法查看名称空间,得到的是一个字典
2.可以使用字典的方式取值
print(Student.__dict__.get('school')) # 清华大学
print(Student.__dict__.get('run')) # <function Student.run at 0x000002102DE2A310>
'在面向对象中,想要获取名称空间的名字可以使用句点符的形式,'由此上述代码可简化为:
print(Student.school)
print(Student.run)
'''类实例化产生对象:类名加括号'''此时的类就像一个制造机,我们加括号'调用'一次,就会产生一个student对象
stu1 = Student()
stu2 = Student()
1.此时的两个学生对象中由于没有各自独有的数据,所以调用__dict__后返回的是空字典
print(stu1.__dict__, stu2.__dict__) # {} {}
2.虽然是空字典,但是他们依然可以访问创造他们的类中的属性
print(stu1.school) # 清华大学
print(stu2.school) # 清华大学
3.两个student对象虽然长得一样,但他们的内存地址不一样
print(stu1) # <__main__.Student object at 0x000001D923B04A60>
print(stu2) # <__main__.Student object at 0x0000025E8A48F130>
4.修改类中的属性,对象中的该属性也会跟着修改
Student.school = '北京大学' # 修改school键对应的值
print(stu1.school) # 北京大学
print(stu2.school) # 北京大学
'我们习惯将类或者对象句点符后面的名字称为属性名或者方法名'
对象独有的数据
class Student:
# 学生对象公共的方法
def run(self):
print('student run')
# 学生对象公共的数据
school = '清华大学'
stu1 = Student()
1.'''推导思路1: 直接利用__dict__方法朝字典添加键值对'''
stu1.__dict__['name'] = 'jason' # 等价于 stu1.name = 'jason'
stu1.__dict__['age'] = 18 # 等价于 obj1.age = 18
stu1.__dict__['gender'] = 'male'
这样一个一个添加属实费时间,若果有多个stu都要添加就太麻烦了
2.'''推导思路2: 将添加独有数据的代码封装成函数'''
def init(obj,name,age,gender):
obj.__dict__['name'] = name
obj.__dict__['age'] = age
obj.__dict__['gender'] = gender
stu1 = Student()
print(stu1.__dict__)
3.'''推导思路3: init函数是专用给学生对象创建独有的数据 其他对象不能调用>>>:面向对象思想 将数据和功能整合到一起
将函数封装到学生类中 这样只有学生类产生的对象才有资格访问
'''
class Student:
# 1.先产生一个空对象
# 2.自动调用类里边的__init__方法,将产生的空对象当做第一个参数传入
# 3.将产生的对象返回出去
def __init__(self, name, age, gender):
self.name = name # 相当于 self.__dict__['name'] = name
self.age = age
self.gender = gender
def run(self): # 功能
print('student run')
school = '清华大学' # 数据
stu1 = Student('jason', 18, 'male')
print(stu1) # <__main__.Student object at 0x00000159E9598FD0>
print(stu1.__dict__) # {'name': 'jason', 'age': 18, 'gender': 'male'}