01 面向对象编程oop __init__ 绑定方法 对战小游戏
一、面向过程和面向对象OOP Object Oriented Programming
1.面向过程
面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。
优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
2.面向对象 OOP
面向对象是一种编程思想,是前辈们总结出的经验,指导程序员如何编写出更好的程序 ,核心是对象,程序就是一系列对象的集合, 程序员负责调度控制这些对象来交互着完成任务:
优点是:
1.扩展性可维护性好 如对游戏中一个人物参数的特征和技能修改都很容易
2.灵活性
缺点:
1.程序的复杂度提高了 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。
2.无法准确预知结果 无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。
应用场景:需求经常变化的软件,对扩展性要求较高的程序 ,通常是直接面向 用户的,例如:qq,微信,游戏
案例:1 把大象装进冰箱 ?
面向过程:
1.打开冰箱
3.关闭冰箱
面向对象:
找个具备装大象的技能的对象
案例2:
在面向对象中程序员的角度发生改变,从具体的操作者变成了指挥者
西天取经
如来有一堆破书要传出去,他没有自己干,而是找了五个对象帮他去干,
如来只要负责控制调度的对象即可 ,
如果某个对象发生变化,也不会影响其他的对象 , 扩展性
案例3:
曹操吟诗
喝酒吃肉,人生真爽
喝酒吃肉,人生几何
对酒当歌,人生几何
面向过程的话每次都要重新再来,面向对象的话采用活字印刷,每次调取一下拼接一下就可以了
二、类和对象
1.类和对象的关系
对象属于某个类
在生活中是先有对象再有类,而在程序中是先有类才能有对象,我们必须先告诉计算机这个类的对象有什么特征、行为
总结出一个结论:在使用面向对象编程时,第一步就是思考需要什么样的对象,对象具备什么样的特征和行为,从而根据 这些信息总结出需要的类
2.定义类和对象
class 类的名称: #类中的内容: 描述属性 和 技能 #描述属性用变量 #描述行为用函数 #类名称 书写规范 首先是见名知意 名称是大驼峰命名法 #驼峰就是单词首字母大写 , 大驼峰是第一个字母大写,小驼峰是第一个字母小写
#类和函数不同,在定义的时候就立刻执行了,只会执行一次
class Person: #定义类
pass
p = Person()#创建对象
3.类和对象 属性的写法
属性写在类中: 类中的属性,是公共的要给所有对象用,不管哪个对象的这个属性id都一毛一样
属性写在对象中: 对象中的属性,是每个对象独特的(不一样的)
属性查找顺序: 如果类中和对象中存在同样的属性,先访问对象自己的, 如果没有再访问类中公共的
练习: 描述一个老师类 需要包含 一个公共属性和 一个独特的属性
class Teacher: name = "jack" t1 = Teacher() t1.age = 28 print(t1.age) #28 print(t1.name) #jack
4.属性的增删改查
增加属性
对象变量名称.属性名称 = 属性值
删除属性
del 对象的变量名称.属性名称
修改属性
对象.属性 = 新的值
查看属性
访问的是对象的所有属性(名字、性别等)
print(对象.__dict__) #__dict__ 相当于查看名称空间,对类也适用
访问对象的类信息
print(对象.__class__)
查看注释 __doc__
5. __init__方法 叫做初始化方法,本质上就是一个函数
特点1: 当实例化 对象(调用类)时,会自动执行init方法
特点2:会自动将对象作为第一个参数传入,参数名称为self ,self 也可以是别的名字,但不建议改
功能:用户给对象赋初始值,和一些别的初始化逻辑(。。。加载成功。。。重启)
注意:该函数不能有任何返回值/.... 只能是None 规定如此..
#给对象添加不同的属性
#第三种 类给我们封装了函数 小轿车是不是很爽? class Teacher: school = "oldboy" def __init__(self, name, age): #对象调用类会首先将自己作为参数self传入 print(self) #<__main__.Teacher object at 0x00000000028D8438> self.name = name #第一个name随便写调用时对应起来就行,表示t1.name=name或者t1.xxx=name,后面的name表示参数与上面的参数一一对应,不能改 self.age = age t1 = Teacher("jack", 28) t2 = Teacher('roce',20)
print(t1.name) #jack print(t1) #<__main__.Teacher object at 0x00000000028D8438> 说明t1就是self #补充 Teacher.school #等于经典类的操作Teacher.__dict__['school'] Teacher.school = 'littleboy' #等于经典类的操作Teacher.__dict__['school']='littleboy' print(Teacher.school) print(t1.school) #littleboy
#第一种 直接添加属性 架子车是不是很难受? class Teacher: school = "oldboy" t1 = Teacher() t1.age = 28 t1.name = 'jack'
t2 = Teacher() t2.age = 20 t2.name = 'roce'
print(t1.age) #28 print(t1.name) #jack #第二种 定义函数添加属性 自行车有没有舒服点? class Teacher: school = "oldboy" def __init__(obj,name,age): obj.name = name obj.age = age
t1 = Teacher() t2 = Teacher() __init__(t1,'roce',20) __init__(t2,'jack',28)
print(t1.age) #28 print(t1.name) #jack
三、绑定方法与非绑定方法
#什么是方法? 函数在面向对象中称之为方法,换种称呼而已! 如此说来,绑定方法也就是绑定函数 #为什么使用方法: 原始的处理方式:函数 传参 问题1 调用函数时传入参数,如果要处理的数据有很多,编写了很多重复的代码,代码的阅读性很差 问题2 后期如果每次处理的数据个数变多了,函数需要修改参数列表,导致以前写的所有代码都需要修改,扩展性非常差 问题3 如果传入了错误的数据,比如需要整型却传入了字符串,造成函数无法正常工作 绑定方法的处理方式: 1.调用方法时传入对象,对象中包含了需要的所有数据,减少重复代码 2.后期数据变化时,修改类对象中的属性,方法中增加相应的处理代码,而方法参数不会发生变化,提高了扩展性 3.方法与对象进行绑定,没有对象则无法使用方法,并且在创建对象的初始化方法中,已经确定了各个属性数据时正确的,如此一来避免了传入使用错误数据执行函数造成的问题
1.绑定方法
绑定给谁:
当函数逻辑需要访问对象中的数据时,绑定给对象
当函数逻辑需要访问类中的数据时,绑定给类
1.1 对象绑定方法
1.需要访问对象里面数据时的方法叫做对象绑定方法,默认情况下类中的方法都是对象绑定方法
2.本质就是一个函数
当使用对象调用该函数时会自动传入对象本身,作为第一个参数
当使用类名来调用时他就是一个普通函数,有几个参数就得传几个参数
练习:写一个学生类,具备一个打招呼的技能 要能输出自己的名字信息
class Student: school = "oldgirl" def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def say_hi(self): print("hello i am a student! my name:%s" % self.name) stu1 = Student("jack",20,"male") stu1.say_hi() #hello i am a student! my name:jack Student.say_hi(stu1) #hello i am a student! my name:jack 使用类名称调用结果一样 print(type(stu1.say_hi)) #<class 'method'> 本质还是函数,只是会把对象作为参数传入 print(type(Student.say_hi)) #<class 'function'> 使用类访问时就是普通函数
1.2 类绑定方法
1.类绑定方法: 用@classmethod来装饰方法
2.特殊之处: 不管用类还是对象调用,都会自动传入类本身,作为第一个参数
#定义一个输出学校的方法
#对象绑定方法(来搞笑的看看就好)
class OldBoyStudent: school = "oldboy" def __init__(self,name): self.name = name def show_school(self): print(self.school) #oldboy 这样虽然也能输出但是我又不访问对象任何信息只需要调用类自己的东西stu.school就可以了,还非要找对象要,不合理啊 stu = OldBoyStudent("jack") stu.show_school() #或者 # OldBoyStudent.show_school(stu) #类绑定方法 class OldBoyStudent: school = "oldboy" def __init__(self,name): self.name = name @classmethod def show_school(cls): print(cls.school) print(cls) stu = OldBoyStudent("jack") stu.show_school() #函数里面cls输出<class '__main__.OldBoyStudent'> OldBoyStudent.show_school() #函数里面cls输出<class '__main__.OldBoyStudent'> print(OldBoyStudent) #<class '__main__.OldBoyStudent'> 三个地方一样说明无论是对象还是类来调用函数,函数里面的cls参数都是传入类本身,即cls=OldBoyStudent
2.非绑定方法 了解就好
又叫做静态方法,就是即不需访问类的数据,.也不需要访问对象的数据
语法:@staticmethod
不常用
class OldBoyStudent: school = "oldboy" def __init__(self,name): self.name = name @staticmethod #谁的数据都不需要用,直接扔到类外面就好,来里面干嘛 def print_hello(): print("hello world") stu = OldBoyStudent("jack") stu.print_hello() #hello world OldBoyStudent.print_hello() #hello world 不管谁来调用,都一样
3.方法 小练习
练习:为学生类添加一个save方法 一个get方法 save是将对象存储到文件中 #要用到对象数据,对象绑定方法 get是从文件中获取对象 #非绑定 import os #任意位置导入模块:输入os alt+两次回车 import pickle import time class Student: def __init__(self,name): self.name = name def say_hi(self): print("name:",self.name) def save(self): with open(self.name,"wb") as f: pickle.dump(self,f) #json不支持序列化对象 @staticmethod #不需要用到类和对象的数据,非绑定 def get(name): with open(name,"rb") as f: obj = pickle.load(f) return obj stu = Student("rose") stu.save() #面向对象的好处,谁用谁调函数就好 obj = Student.get("rose") print(obj.name)
四、面向对象 对战游戏
import random import time class Hero: def __init__(self,name,level,blood,att,q_hurt,w_hurt,e_hurt): # 简便写法 lcs = locals() lcs.pop("self") self.__dict__.update(lcs) def attack(self,enemy): enemy.blood -= self.att print("%s对%s释放了普通攻击 造成了%s的伤害 敌人剩余血量%s" % (self.name, enemy.name, self.att, enemy.blood)) if enemy.blood <= 0: print("%s被%s使用普通攻击击杀了" % (enemy.name,self.name)) def Q(self,enemy): enemy.blood -= self.q_hurt print("%s对%s释放了Q 造成了%s的伤害 敌人剩余血量%s" % (self.name, enemy.name, self.q_hurt, enemy.blood)) if enemy.blood <= 0: print("%s被%s使用Q技能击杀了" % (enemy.name, self.name)) def W(self,enemy): enemy.blood -= self.w_hurt print("%s对%s释放了W 造成了%s的伤害 敌人剩余血量%s" % (self.name, enemy.name, self.w_hurt, enemy.blood)) if enemy.blood <= 0: print("%s被%s使用W技能击杀了" % (enemy.name, self.name)) def E(self,enemy): enemy.blood -= self.e_hurt print("%s对%s释放了E 造成了%s的伤害 敌人剩余血量%s" % (self.name,enemy.name,self.e_hurt,enemy.blood)) if enemy.blood <= 0: print("%s被%s使用E技能击杀了" % (enemy.name, self.name)) h1 = Hero("亚索",20,2000,100,600,0,1000) h2 = Hero("妲己",20,2000,100,600,500,1000) h3 = Hero("鲁班",20,1500,700,100,200,300) h4 = Hero("蔡文姬",20,2000,10,0,0,10) # # h1.attack(h2) # h2.Q(h1) # h2.E(h1) # h2.W(h1) #从字典中随机拿出一个值 def random_hero(heros): hero_index = random.randint(1, len(heros)) return heros[hero_index] while True: # # 把所有的攻击方法装到字典里 为了随机取出一个 funcs = {1: Hero.Q, 2: Hero.W, 3: Hero.E, 4: Hero.attack} func_index = random.randint(1, 4) func = funcs[func_index] # 把所有的英雄方法装到字典里 为了随机取出一个 heros = {1: h1, 2: h2, 3: h3, 4: h4} hero = random_hero(heros) # 剩余的英雄们 other_heros = {} new_index = 1 for k, v in heros.items(): if v != hero: other_heros[new_index] = v new_index += 1 # 从剩余的英雄中随机挑出一个英雄来挨打 enemy = random_hero(other_heros) # 打他 func(hero, enemy) if enemy.blood <= 0: break time.sleep(0.5)
#类里面的方法(函数)不能直接拿方法名,对象.方法名=之前的函数名,再加括号传入参数开始执行
#类里面[func1,func2,func3]会报错,应该写成[h1.func1,h1.func2,h1.func3],字典里面也是这样
import random import time class Hero: def __init__(self,name,hp,damage,Q,W,E,R): self.name = name self.hp = hp self.damage = damage self.Q = Q self.W = W self.E = E self.R = R def normal_attack(self,enemy): enemy.hp -= self.damage print('%s普攻打了%s%s滴血'%(self.name,enemy.name,self.damage)) time.sleep(0.6) def Q_attack(self,enemy): enemy.hp -= self.Q print('%sQ打了%s%s滴血'%(self.name,enemy.name,self.Q)) time.sleep(0.6) def W_attack(self,enemy): enemy.hp -= self.W print('%sW打了%s%s滴血'%(self.name,enemy.name,self.W)) time.sleep(0.6) def E_attack(self,enemy): enemy.hp -= self.E print('%sE打了%s%s滴血'%(self.name,enemy.name,self.E)) time.sleep(0.6) def R_attack(self,enemy): enemy.hp -= self.R print('%sR打了%s%s滴血'%(self.name,enemy.name,self.R)) time.sleep(0.6) h1 =Hero('蛮王',30000,3550,3000,1000,4000,5000) h2 =Hero('提莫',30000,2000,7000,1000,4000,5000) h3 =Hero('狗熊',30000,2000,3000,1000,4000,5000) h4 =Hero('剑圣',30000,2000,3000,3000,4000,3000) h5 =Hero('赵信',30000,2000,3000,1000,4000,3000) h6 =Hero('瑞文',30000,2000,3000,3000,4020,3000) h7 =Hero('寡妇',30000,2000,3000,3000,4000,3000) # h1.normal_attack(h2) # h1.Q_attack(h2) # h1.W_attack(h2) # h1.E_attack(h2) # h1.R_attack(h2) hero_list =[h1,h2,h3,h4,h5,h6,h7] while True: self_obj = random.choice(hero_list) enemy_obj = random.choice(hero_list) if self_obj == enemy_obj:continue else: attack_kind_list = [self_obj.normal_attack, self_obj.Q_attack, self_obj.W_attack, self_obj.E_attack, self_obj.R_attack, ] #类里面的方法(函数)不能直接拿方法名,对象.方法名=之前的函数名,再加括号传入参数开始执行 attack_kind = random.choice(attack_kind_list) attack_kind(enemy_obj) if enemy_obj.hp <= 0: print('%s使用击杀了%s'%(self_obj.name,enemy_obj.name)) break