面向对象变成介绍
面向过程编程
核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。主要应用在一旦完成很少修改的地方,如linux内核、git、apache服务器等
优点:极大的降低了程序的设计复杂度
缺点:可扩展性差,改动一个地方很可能要改多个地方,牵一发而动全身
面向对象编程:不是编程的全部,只是用来解决软件可扩展性的
核心是对象(上帝式思维),对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象就是把计算机程序作为一个对象集合,每隔对象都能接收其他对象传来的消息,计算机程序的执行就是就是一系列消息在各个对象之间传递。主要应用在需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等
优点:解决了程序的可扩展性差的问题,对某一个对象修改会立刻反映到整个程序体系,如lol对一个英雄属性的修改(万年削弱从未被加强)
缺点:可控性差,无法预测程序执行结果,如lol每局游戏的结果
类和对象
对象的概念
对象也叫做实例,是通过类产生的,对象就是数据属性和方法属性的结合体,比如lol中的英雄,一个英雄就是一个对象,具备生命值、蓝条攻击力等数据属性,又具备qwer四个技能属于方法属性。
类的概念
类包含了一组对象的相同属性部分,还是说lol中的英雄,所有的英雄都有名字、标签(坦克战士法师)、生命值、蓝条攻击力等,虽然具体的值不同,但是都可以包含在类中去生成。
在python中,用变量表示是数据属性,用函数表示方法属性。
在现实生活中,先有对象后有类,比如先有的人,后有的人类的概念,而在编程中,是先有的类,后有的对象,类生成对象。
声明一个类:和函数的定义类似
定义函数:通过def关键字
1 def functionName(args):2 '函数文档字符串'3 函数体
定义类:通过cless关键字,类的名字一般开头大写,为了和函数区分
1 '''2 class 类名:3 '类的文档字符串'4 类体5 '''6 #我们创建一个类Data类,7 class Data:8 pass
示例:定义一个中国人的类
首先分析中国人的特征,首先国籍必须是属于中国,然后还要是个人,那么人都会做什么(吃喝拉撒睡说话)等等,另外每个人还有自己特有的属性,比如名字身高生日等
伪代码分析(只是分析):
chinese# 例如共有属性有country='china' #国籍中国language='chinese' #语言为中文,地方语言、少数民族语言可列为特有属性# 会的技能def talk(self): print('is talking')def eat(self): print('is eating')def sleep(self): print('is eating')def work(self): print('is working')# 等一系列人类含有的技能 #特有属性 name=name age=age sex=sex
代码定义:__init__函数和self会在下面说明
1 class Chinese: 2 # 共同的特征:国籍和语言 3 country = 'China' 4 language = 'chinese' 5 # __init__(p1, 'zhangsan', 'man', 73) 6 def __init__(self,name,sex,age): 7 #只用于初始化的活,不能有返回值,用于定义特有的属性 8 self.name=name #p1.name='zhangsan' 9 self.sex=sex #p1.sex='man'10 self.age=age #p1.age=7311 # 共同的技能12 def talk(self):13 print('is talking')14 def eat(self):15 print('is eating')16 def sleep(self):17 print('is eating')18 def work(self):19 print('is working')
类的属性引用:
1 print(Chinese.__dict__) #以字典的方式返回Chinese的所有属性,或者用dir(类名)查询2 print(Chinese.language) #查看Chinese的language属性3 print(Chinese.work) #查看Chinese的work属性4 Chinese.language='putonghua' #修改类的language属性5 Chinese.complexion='yellow' #增加一条肤色属性6 del Chinese.complexion #删除肤色属性
实例化(生成实例):__init__函数和self说明
1 p1=Chinese('zhangsan','man',73) #生成一个实例,name='zhangsan' sex='man' age=732 print(p1.__dict__) #以字典的方式返回p1的属性,不包含类的共同属性,只有name/sex/age
类名加上括号就是一个实例化的过程,以上生成了一个叫做p1的对象
1 print(p1.__dict__,type(p1))2 #输出结果为3 {'name': 'zhangsan', 'sex': 'man', 'age': 73}
p1.__dict__输出的结果为__init__函数所执行后的结果,而__init__函数的位置参数有四个self,name,sex,age,实例化的过程中传入了'zhangsan','man',73三个参数对应name,sex,age,而self也是一个位置参数,函数部分我们知道位置参数必须要传入值,这里代码自动会把值p1传入,也就是实例的名字p1。
而p1的类型为<class '__main__.Chinese'>,如果查看Chinese的类型会发现和p1的一样,也就是说定义一个类也就是定义了一个类型。
对象的属性引用:
1 print(p1.language) #p1本身并没有language属性,但是可以查到,自己本身虽然没有,但是从类里可以拿到2 # print(p1.run()) #报错,因为自己和类里都没有这个属性3 print(p1.work())4 p1.city='beijing' #增加一条属性,只是针对与对象本身,会增加到p1的属性字典里,不会对Chinese有影响5 del p1.city #删除一条属性
结论:
类的数据属性可以增删查改
对象的数据属性可以增删查改
对象本身并没有函数属性,只有自有的数据属性(__init__函数初始化的属性或者手动添加的属性),但是可以通过类调用到,也就是共有的特征和属性
对象里通用的数据和函数是引用类的名称空间
对象的属性,优先从自己的__dict__字典里找,如果自己的字典里没有,访问类里的,如果类里也没有,报错,自己定义的属性对其他引用了类的相同属性没有影响
对象的用法:
class Chinese: obj_list=[] count=0 country = 'China' language = 'chinese' def __init__(self,name,sex,age): self.name=name self.sex=sex self.age=age self.obj_list.append(name) #每次实例化,实例都往pbj_list中添加一个名字 self.count+=1 #每次实例化,实例都将count+1 def sleep(self): print('is eating') def work(self): print('is working')p1=Chinese('bob','man',18)p2=Chinese('natasha','woman',28)p3=Chinese('hurry','man',10)print(p1.obj_list,p1.__dict__)print(p2.obj_list,p2.__dict__)print(p3.obj_list,p3.__dict__)print(Chinese.obj_list)#######分割线君#######print(p1.count,id(p1.count))print(p2.count,id(p2.count))print(p3.count,id(p3.count))print(Chinese.count,id(Chinese.count))输出结果:['bob', 'natasha', 'hurry'] {'name': 'bob', 'sex': 'man', 'age': 18, 'count': 1}['bob', 'natasha', 'hurry'] {'name': 'natasha', 'sex': 'woman', 'age': 28, 'count': 1}['bob', 'natasha', 'hurry'] {'name': 'hurry', 'sex': 'man', 'age': 10, 'count': 1}['bob', 'natasha', 'hurry']1 18186046081 18186046081 18186046080 1818604576
结果发现:每个实例都没有obj_list,但是却有count,一个是可变,一个是不可变,所以类里可变的数据obj_list可以直接被修改,内存id不会改变,而不可变的数据count只能被重新计算,开辟新的内存空间进行引用,是建立在对象的属性中,而非类的属性
1 class Chinese: 2 obj_list=[] 3 count=0 4 country = 'China' 5 language = 'chinese' 6 def __init__(self,name,sex,age): 7 self.name=name 8 self.sex=sex 9 self.age=age10 Chinese.obj_list.append(name) #每次实例化,Chinese类都往obj_list中添加一个实例名字11 Chinese.count+=1 #每次实例化,Chinese都将count+112 def sleep(self):13 print('is eating')14 def work(self):15 print('is working')16 p1=Chinese('bob','man',18)17 p2=Chinese('natasha','woman',28)18 p3=Chinese('hurry','man',10)19 print(p1.obj_list,p1.__dict__)20 print(p2.obj_list,p2.__dict__)21 print(p3.obj_list,p3.__dict__)22 print(Chinese.obj_list)23 #######分割线君#######24 print(p1.count,id(p1.count))25 print(p2.count,id(p2.count))26 print(p3.count,id(p3.count))27 print(Chinese.count,id(Chinese.count))28 29 输出结果30 ['bob', 'natasha', 'hurry'] {'name': 'bob', 'sex': 'man', 'age': 18}31 ['bob', 'natasha', 'hurry'] {'name': 'natasha', 'sex': 'woman', 'age': 28}32 ['bob', 'natasha', 'hurry'] {'name': 'hurry', 'sex': 'man', 'age': 10}33 ['bob', 'natasha', 'hurry']34 3 181860467235 3 181860467236 3 181860467237 3 1818604672
结果发现:在实例化的过程中,类本身做的属性修改操作,不会对对象造成任何影响,所有的对象都不包含count属性和obj_list属性,都是通过类去调用的。
1 class Chinese: 2 country = 'China' 3 language = 'chinese' 4 def __init__(self,name,sex,age): 5 self.name=name 6 self.sex=sex 7 self.age=age 8 def sleep(self): 9 print('%s is eating' %self.name)10 def work(self):11 print('%s is working' %self.name)12 p1=Chinese('bob','man',18)13 p2=Chinese('natasha','woman',28)14 # Chinese.work() #抛出TypeError15 p1.work()16 p2.work()17 #输出结果18 bob is working19 natasha is working
结果发现:类里面定义的函数只是给对象用的,类本身无法使用这些函数。类定义的只是方法,而方法是给对象绑定的。
类的继承与派生
经典类和新式类
在python3中,所有类默认继承object,但凡是继承了object类的子类,以及该子类的子类,都称为新式类(在python3中所有的类都是新式类)
没有继承object类的子类成为经典类(在python2中,没有继承object的类,以及它的子类,都是经典类)
1 class People:2 pass3 class Animal:4 pass5 class Student(People,Animal): #People、Animal称为基类或父类,Student为子类,Student继承了People和Animal的所有属性6 pass7 print(Student.__bases__) #__bases__方法,查看继承的类的元组8 print(People.__bases__)9 print(Animal.__bases__)
输出结果:
1 (, ) #继承了两个父类2 ( ,) #默认继承了object类3 ( ,)
继承
继承是为了减少代码重用的问题,以减少代码冗余。
继承是一种是什么是什么的关系,例如老师类是人类,而非老师类是生日类
继承类示例:
1 class People: #定义父类People 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 def walk(self): 6 print('%s is walking' %self.name) 7 8 #Teacher类和Student类无任何属性 9 class Teacher(People): #Teacher类继承People类的属性10 pass11 class Student(People): #Student类继承People类的属性12 pass
引用测试:
1 t=Teacher('bob',18) #实例化一个Teacher对象,而非People对象,Student子类同理 2 print(type(t)) 3 print(t.name,t.age) 4 print(t.__dict__) 5 t.walk() #Teacher子类继承了People的属性,使得Teacher子类的对象能够调用到父类的属性 6 7 输出结果: 89 bob 1810 {'name': 'bob', 'age': 18}11 bob is walking
派生
派生是在子类继承父类的基础上, 定义子类独有的属性,例如Teacher可以有教师等级的划分、有教学课程的划分,但是继承父类People类是没有等级和课程的划分的。
示例:
1 #定义父类People 2 class People: 3 def __init__(self, name, age,sex): 4 self.name = name 5 self.age = age 6 self.sex=sex 7 def walk(self): 8 print('%s is walking' % self.name) 9 def test(self):10 print('test class from father class %s' %self.name)11 #定义Teacher子类12 class Teacher(People):13 school = 'jialidun'14 def __init__(self, name, age,sex,level,salary):15 People.__init__(self,name,age,sex) #继承父类的初始化内容,实例化时候接收的参数name、age、sex会传给People.__init__16 self.level=level #派生的独有属性17 self.salary=salary #派生的独有属性18 def teach(self): #派生的独有属性19 print('%s is teaching' %self.name)20 def test(self): #派生父类的已有属性,对象在进行属性引用的时候会优先引用实例化过程中用到的类21 People.test(self)22 print('from teacher')23 #定义Student子类24 class Student(People):25 def __init__(self, name, age,sex,group):26 People.__init__(self, name, age, sex)27 self.group=group28 def study(self):29 print('%s is studying' %self.name)
测试验证:
1 t=Teacher('natasha',18,'male',10,3000) #__init__(t,'natasha',18,'male',10,3000)2 print(Teacher.__bases__)3 print(Teacher.__dict__)4 t.test()
组合
不同于继承,组合是包含的意思,表示一种什么有什么的关系,也是为了减少重复代码的
示例:还是People、Teacher和Student的例子,只是加上了一个Birthday生日类
1 #Birthday类,需要传入年月日 2 class Birthday: 3 def __init__(self,year,mon,day): 4 self.year=year 5 self.mon=mon 6 self.day=day 7 def tell_birth(self): 8 print('出生于<%s>年 <%s>月 <%s>日' % (self.year,self.mon,self.day)) 9 #People类,需要接受名字年龄年月日,年月日传给Birthday类10 class People:11 def __init__(self, name, age, year, mon, day):12 self.name = name13 self.age = age14 #__init__接收的year, mon, day传给Birthday类15 self.birth = Birthday(year, mon, day) #包含Birthday类,生日不只是人类才有,其他动物也可以有生日,不同于继承16 def walk(self):17 print('%s is walking' % self.name)18 #Teacher类19 class Teacher(People):20 def __init__(self, name, age, year, mon, day,level,salary):21 #__init__接收的name, age, year, mon, day传给People类22 People.__init__(self,name,age,year,mon,day)23 self.level=level24 self.salary=salary25 def teach(self):26 print('%s is teaching' %self.name)27 #Student类28 class Student(People):29 def __init__(self, name, age, year, mon, day,group):30 People.__init__(self,name,age,year,mon,day)31 self.group=group32 def study(self):33 print('%s is studying' %self.name)
测试验证:
1 t=Teacher('hurry',18,1990,2,33,10,3000) #传入的值为Teacher类接收的值2 print(t.name,t.age) #对象t的名字和年龄3 print(t.birth) #输出的是一个类对象,因为父类People定义的birth属性就是一个类Birthday4 t.birth.tell_birth() #查看对象t所继承的People类的birth属性(Birthday类)的tell_birth()属性5 print(t.birth.year)6 print(t.birth.mon)7 print(t.birth.day)
接口和抽象类
接口
接口是一组功能的入口,要调用某一组功能,需要通过接口来进行调用,而不需要关注这组功能是如何实现的,要的只是结果。
在类里,接口是提取了一群类共同的函数,可以把接口当做一个函数的集合。
python模仿接口示例:
1 #模仿Linux内文件读写的接口,Linux不管是文本,还是磁盘还是进程都是通过文件去实现的,只不过方法不同,但是没关系 2 class File: #定义一个接口类,提供read和write方法,但是一定是pass没有处理过程的,因为功能的实现具体靠的是子类 3 def read(self): #定接口函数read 4 pass 5 def write(self): #定义接口函数write 6 pass 7 #定义子类实现读写功能 8 #文本文件的读写 9 class Txt(File): #文本,具体实现read和write10 def du(self): #注意并不是read11 print('文本数据的读取方法')12 def xie(self): #注意并不是write13 print('文本数据的写入方法')14 #硬盘数据的读写15 class Sata(File): #磁盘,具体实现read和write16 def read(self):17 print('硬盘数据的读取方法')18 def write(self):19 print('硬盘数据的写入方法')20 #进程数据的读写21 class Process(File):22 def read(self):23 print('进程数据的读取方法')24 def write(self):25 print('进程数据的写入方法')
测试验证:硬盘和进程一样,所以制作文本和硬盘的测试即可
硬盘读写测试:
1 disk=Sata() #实例化一个硬盘读写对象2 disk.read() #硬盘读3 disk.write() #硬盘写4 5 输出结果:6 硬盘数据的读取方法7 硬盘数据的写入方法
文本读写测试:执行后会发现没有任何输出,那是因为txt对象实际上访问的read和write属性并非子类Txt所提供的属性,Txt所提供的属性只是du和xie,但是txt对象有read和write属性,别忘了Txt类是继承了父类File的属性,所以实际上txt对象的read和write属性是父类File提供的
1 txt=Txt()2 txt.read()3 txt.write()
正确的做法是将Txt类的du和xie方法改成read和write方法,这么做的意义为归一化
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
抽象类
抽象类的本质上也是类,但是抽象类只能够被继承,不能进行实例化,也就是说可以当父类,但是不能生成对象。
抽象类介于接口和归一化中间,用于实现接口的归一化
当子类继承抽象类的时候,如果抽象类定义了抽象方法,那么子类必须要定义同名的方法。即父类限制:
1、子类必须要有父类的方法
2、子类实现的方法必须跟父类的方法的名字一样
python的抽象类通过abc模块实现。
接口归一化示例:
1 import abc 2 class File(metaclass=abc.ABCMeta): #metaclass指的是元类,边会讲,现在只需记住这个词 3 @abc.abstractmethod #抽象方法,即一个装饰器装饰read属性 4 def read(self): 5 pass 6 @abc.abstractmethod #抽象方法,即一个装饰器装饰write属性 7 def write(self): 8 pass 9 # # 当继承File类时候,如果没有read和write方法,会提示出错TypeError: Can't instantiate abstract class Txt with abstract methods read, write10 # class Txt(File):11 # def du(self):12 # print('文本数据的读取方法')13 # def xie(self):14 # print('文本数据的写入方法')15 #定义子类具体实现文本的读写操作16 class Txt(File):17 def read(self):18 print('文本数据的读取方法')19 def write(self):20 print('文本数据的写入方法')21 #定义子类具体实现硬盘的读写操作22 class Sata(File):23 def read(self):24 print('硬盘数据的读取方法')25 def write(self):26 print('硬盘数据的写入方法')27 #定义子类具体实现进程的读写操作28 class Process(File):29 def read(self):30 print('进程数据的读取方法')31 def write(self):32 print('进程数据的写入方法')
测试验证:
1 t=Txt() 2 t.read() 3 t.write() 4 s=Sata() 5 s.read() 6 s.write() 7 输出结果: 8 文本数据的读取方法 9 文本数据的写入方法10 硬盘数据的读取方法11 硬盘数据的写入方法
继承+组合应用示例
1 class Date: #定义时间类,包含姓名、年、月、日,用于返回生日 2 def __init__(self,name,year,mon,day): 3 self.name = name 4 self.year=year 5 self.mon=mon 6 self.day=day 7 def tell_birth(self): 8 print('%s:%s-%s-%s'%(self.name,self.year,self.mon,self.day)) 9 10 11 class Course: #定义课程类,包含姓名,课程名、价格,用于返回学生报的课程信息或老师的教学信息12 def __init__(self,name,price,period):13 self.name=name14 self.price=price15 self.period=period16 def tell_course(self):17 print('''18 --------%s course info------------19 course name:%s20 course price:%s21 course period:%s22 '''%(self.name,self.name,self.price,self.period))23 class People: #定义父类People,减少学生类和老师类的代码量24 def __init__(self,name,age,sex,year,mon,day):25 self.name=name26 self.age=age27 self.sex=sex28 self.courses=[] #用于存放课程名称,如果没有这个默认值,下边course_info可以用if判断29 self.birth=Date(name,year,mon,day)30 def walk(self):31 print('%s is walking' %self.name)32 def course_info(self): #33 # if 'courses' in self.__dict__: 可以是判断对象是否包含课程信息,加个默认值就肯定有courses34 for obj in self.courses: #循环查看课程信息,即课程对象的tell_course属性35 obj.tell_course()36 class Teacher(People):37 def __init__(self,name,age,sex,salary,level,year,mon,day):38 People.__init__(self,name,age,sex,year,mon,day) #因为父类的初始化需要这些参数,如果不初始化会报错39 self.salary=salary40 self.level=level41 def teach(self):42 print('%s is teaching' %self.name)43 def tell_info(self):44 print('''45 --------------%s info--------------46 NAME:%s47 AGE:%s48 SEX:%s49 SAL:%s50 LEVEL:%s51 '''%(self.name,self.name,self.age,self.sex,self.salary,self.level))52 53 class Student(People):54 def __init__(self,name,age,sex,group,year,mon,day):55 People.__init__(self,name, age, sex,year,mon,day)56 self.group=group57 def tell_info(self):58 print('''59 --------------%s info--------------60 NAME:%s61 AGE:%s62 SEX:%s63 GROUP:%s64 '''%(self.name,self.name,self.age,self.sex,self.group))
测试验证:
1 bob=Teacher('bob',84,'female',300000,-1,1994,5,27) 2 print(bob.__dict__) 3 #输出 4 {'name': 'bob', 'age': 84, 'sex': 'female', 'courses': [], 'birth': <__main__.Date object at 0x000001E117677278>, 'salary': 300000, 'level': -1} 5 6 bob.birth.tell_birth() 7 #输出 8 bob:1994-5-27 9 10 python=Course('Python',15800,'5month') #定义课程对象11 linux=Course('Linux',12800,'4month')12 bob.courses.append(python) #将课程对象加到bob的课程列表中13 bob.courses.append(linux)14 bob.course_info() #调用bob的course_info属性,循环输出课程对象的tell_course属性,如果课程列表中没有课程对象,那么就没有输出15 #输出16 --------Python course info------------17 course name:Python18 course price:1580019 course period:5month20 21 22 --------Linux course info------------23 course name:Linux24 course price:1280025 course period:4month26 27 # bob.courses[0].tell_course(),这是用很low的方法查看课程信息
对象的序列化和反序列化
py对象的序列化是基于pickle模块完成的,pickle模块支持所有的py数据的序列化,json模块只能够支持各种编程语言通用的数据类型
对象基于文件反序列化,需要生成该对象的类存在于内存中,并且没有被隔离
序列化示例:
创建类文件
1 #创建student_class.py文件,定义一个类 2 class Student: 3 def __init__(self, name, age, sex, group): 4 self.name=name 5 self.age=age 6 self.sex=sex 7 self.group=group 8 def study(self): 9 print('%s is study' % self.name)10 def tell_info(self):11 print('''12 ----------%s info---------13 NAME:%s14 AGE:%s15 SEX:%s16 group:%s17 ''' %(self.name,self.name,self.age,self.sex,self.group))
创建序列化执行文件:二进制写入的文件student.pkl需要用二进制方式才能打开
1 #创建serialize.py文件2 import pickle #导入pickle模块3 import student_class #导入上面的类模块4 with open('student.pkl','wb') as file:5 student1=student_class.Student('bob',15,'male','T3') #创建对象6 pickle.dump(student1,file) #序列化对象写入文件student.pkl
创建反序列化文件:
1 #创建反序列化执行文件deserialize.py2 import pickle3 import student_class4 with open('student.pkl','rb') as file:5 print(file.read())6 #测试输出结果:7 # b'\x80\x03cstudent_class\nStudent\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00bobq\x04X\x03\x00\x00\x00ageq\x05K\x0fX\x03\x00\x00\x00sexq\x06X\x04\x00\x00\x00maleq\x07X\x05\x00\x00\x00groupq\x08X\x02\x00\x00\x00T3q\tub.'8 bob=pickle.load(file) #用load反序列化不能fild.read(),否则会提示EOFError:Ran out of input,因为file.read()已经将文件读完了9 bob.tell_info()