四.面向对象和函数补充
一.面向对象的程序设计:
面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。
优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
二.类与对象:
类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看
在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
class school: logo = 'Oldboy' def __init__(self,addr,phone): self.Members_teacher=[] self.addr=addr self.phone=phone def enroll_student(self): print('python课程培训') class teacher: country = 'China' def __init__(self,name,age,sex,phone,province,salary,Students_list,course): self.name=name self.age=age self.sex=sex self.phone=phone self.province=province self.salary=salary self.Students_list=Students_list self.course=course def teach(self): print('开始教课') def hand_in(self): print('开始评分') class student: country = 'China' def __init__(self,name,age,sex,phone,province,course): self.name = name self.age = age self.sex = sex self.phone = phone self.province = province self.course = course def handin(self): pass def check(self): print('查看成绩!') choice_list = ['teacher','student','school'] for i,j in enumerate(choice_list,1): print(i,j) user_choice = input('请输入你的选择:').strip() def choice1(x): if x == '1': print('''%s 该校区的地址是: %s 该校区的电话是:%s''' % (school.logo,school('昌平','').addr,school('','01221-12212121').phone)) choice1(user_choice)
补充:
在python3中所有的类都是新类,而在Python2中有新类和经典类之分。
python3:class name:这就是新类,
python2:class name(object): 新类
class name: 没有继承,就是经典类 name.__bases__ 查看继承
#python为类内置的特殊属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性(名称空间)
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
从代码层级看面向对象:
#1、在没有学习类这个概念时,数据与功能是分离的 def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx #每次调用都需要重复传入一堆参数 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字') #2、我们能想到的解决方法是,把这些变量都定义成全局变量 HOST=‘127.0.0.1’ PORT=3306 DB=‘db1’ CHARSET=‘utf8’ def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') exc2(HOST,PORT,DB,CHARSET,'存储过程的名字') #3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset def exc1(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.execute(sql) return res def exc2(self,sql): conn=connect(self.host,self.port,self.db,self.charset) res=conn.call_proc(sql) return res obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字') #改进 class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset self.conn=connect(self.host,self.port,self.db,self.charset) def exc1(self,sql): return self.conn.execute(sql) def exc2(self,sql): return self.conn.call_proc(sql) obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字') 数据与专门操作该数据的功能组合到一起
三.类的属性:类的属性有数据属性和函数属性,也相当于变量(特征)和技能(函数)
在新建一个新的类时,会产生一个新的名称空间
1.数据属性是所有对象都所共享的。
__init__()内的属性是对象所特有的特征
2.函数属性是给所有对象使用的。
在python中,类是变量(特征)和函数属性(技能)的结合体,对象是变量(共有特征和独有特征)和方法的结合体
3.类和对象的引用方法:
类的引用方法
1.实例化:class name: >>>>>>> g1=name(g1,age,addr) 相当于先执行 name__init__(g1,age,addr) 再执行__init__内的代码
在类中,name.object,先从类名称空间中找object,如果还找不到再从父类中找object,最后再找不到就报错。
实例化产生的对象也可以进行增,查,改,删
2.属性引用:变量和函数
类的属性可以进行,增,查,改,删
对象的引用方法:
在对象中,objiect.name会先从自己的名称空间内找name,找不到就在类中找name,如果还找不到再从父类中找name,最后再找不到就报错
对于绑定方法而言,类会将函数属性绑定要对象上,当对象操作函数属性时操作的是对象本身的绑定方法,如g1.name(*args,**kwargs) 就是将self自身传进去绑定name方法
在对象之间而言,对变量的特征(__init__定义的属性)引用时引用的是不同的内存地址下变量。
四.类的继承和派生,组合
1.继承:继承的作用是用来解决代码重用性的,也是使用继承的原因
继承指的是类与类之间的关系,是一种什么是什么的关系,功能之一就是用来解决代码重用问题
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
如代码:
class Hero: def __init__(self,nickname,aggressivity,life_value): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value def move_forward(self): print('%s move forward' %self.nickname) def move_backward(self): print('%s move backward' %self.nickname) def move_left(self): print('%s move forward' %self.nickname) def move_right(self): print('%s move forward' %self.nickname) def attack(self,enemy): enemy.life_value-=self.aggressivity class Garen(Hero): pass class Riven(Hero): pass g1=Garen('草丛伦',100,300) r1=Riven('锐雯雯',57,200) print(g1.life_value) r1.attack(g1) print(g1.life_value) ''' 运行结果 243 '''
子类继承父类中变量和函数属性
2.派生:在继承一个父类时,定义一个与父类函数属性名称相同的只属于自己的函数属性,在调用该属性时,调用自己的函数属性,这种方式成为派生
派生的方式:1.父类存在的函数属性情况下,派生重名的函数属性
2.父类不存在某个函数属性,在子类中定义一个新的函数属性
3.调用父类中函数属性部分功能的情况,在自己的__init__函数里面调用部分父类的函数属性
*****在子类中调用父类的函数属性时,可以使用super()函数将 父类名.__init__(self,)替代,不用再传self
super()函数的用法:1.在python3中,直接super().父类的函数属性的名字。
2.在python2中,新式类与python3相同,对于经典类而言,使用方法为:super(自己的类名,self).父类的函数属性的名字
class Hero: def __init__(self,nickname,aggressivity,life_value): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value
class Riven(Hero): camp='Noxus' def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能 self.skin=skin #新属性 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 Hero.attack(self,enemy) #调用功能 print('from riven') def fly(self): #在自己这里定义新的 print('%s is flying' %self.nickname) r1=Riven('锐雯雯',57,200,'比基尼') r1.fly() print(r1.skin) ''' 运行结果 锐雯雯 is flying 比基尼 '''
3.组合:组合是解决重用关系的一种方式
class Teacher: def __init__(self,name,age,course,birth): self.name = name self.age = age self.course = course self.birth = birth class Student: def __init__(self,name,age,course,birth): self.name = name self.age = age self.course = course self.birth = birth class Course: def __init__(self,course_name,course_price,course_period): self.course_name = course_name self.course_price = course_price self.course_periode =course_period class Birthday: def __init__(self,year,month,date): self.year = year self.month = month self.date = date course = Course('python','15800','7m') birth = Birthday('1991','10','2') birth1 = Birthday('2001','2','7') teacher = Teacher('yuan','22',course,birth) student = Student('deng',24,course,birth1) print(teacher.course.course_name,teacher.course.course_price,teacher.course.course_periode,teacher.birth.year + teacher.birth.month + teacher.birth.date) print(student.course.course_name,student.course.course_price,student.course.course_periode,student.birth.year + student.birth.month + student.birth.date) 运行结果:>>>>>>> python 15800 7m 1991102 python 15800 7m 200127
teacher类和student类就重用了date,birthday2个类,这种解决重用关系的方式就是组合
继承和组合有什么区别:
继承是指什么是什么,如 teacher是people,teacher要用people中的函数属性就用继承方式;
组合是指什么有什么,如teacher有birthday,teacher要用birthday中的函数属性就用组合方式;因为teacher和birthday不在一哥抽象概念里面,所以用组合
4.继承原理:
python3: 继承顺序为深度优先,广度优先 调用name.__mor__可以查看继承原理
在pyhton2:新式类与pyhton3是一样的,而对于经典类而言,只遵循深度优先。
五.统一接口与归一化的设计
接口的定义:提供给使用者统一的函数属性的方式为接口。
使用接口的原因:
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
3.由于python没有接口的关键字,所有可以用类来模仿接口:
因为在不使用抽象类时模仿的接口只是看起来像一个接口,并没有起到接口的作用,子类完全不用去实现接口,所以必须在父类里面定义子类必须有父类的函数属性。
使用抽象属性才能模仿接口,进行归一化设计.
抽象类的含义:import abc 模块
本质上是一个类,其次是子类必须实现加了abcstructmehod装饰器的函数属性,否则报错。
#_*_coding:utf-8_*_ #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
六.多态与封装
1.多态的概念:多态是指一种事物的多种形态2.多态性:定义统一的接口,多态性依赖于继承
一种的调用方式,不同的执行效果就是多态性
def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值
obj(run()) #调用的了逻辑都一样,执行的结果却不一样
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao') >>> class Cat(Animal): #属于动物的另外一种形态:猫 ... def talk(self): ... print('say miao') ... >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 ... animal.talk() ... >>> cat1=Cat() #实例出一只猫 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ''' 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) '''
3.封装
封装可以进行:1.数据的封装:2.方法的封装:
--语法:只在定义的时候才会有变形的结果,如果对象,类已经产生就不会有变形的效果。
如:class func:
--x = 1 #变形成_func__x = 1
def run(self): #变形成 _func__run(self)
print('aaaa")
s1 =func()
print(s1.x) 调不到X的值,必须print(s1._func__x)才能调用X的值 ,函数属性也一样
4.property,staticmethod,classmethod
1.property:被property装饰的属性会优先于对象的属性被使用
作用:1.使对象对类变量和函数属性的调用方式统一化 2.将函数属性伪装成一个数据属性
使用方式:作为装饰器使用,在作为装饰器使用之后也可以在其下定义name.setter,name.deltter,可以对类的变量属性进行修改和删除
class BIM:
def __init__(self,name,age,weight,height):
self.name = name # self.name = 'ssss' >>>> b1.name = 'ssss' 就是赋值,调用setter下的name函数
self.__age = age
self.__weight = weight
self.__height = height
@property
def obesityindex(self):
return self.__weight/(self.__height**2)
@property
def name(self):
return self.__name
@name.setter
def name(self,value):
if not isinstance(value,str):
raise TypeError('请输入正确的格式!') #抛出异常
self.__name = value
@name.deleter
def name(self):
del self.__name
>>>>>>>>>
sss
22.530864197530864
deng
yuan
Traceback (most recent call last):
File "C:/Users/dsy/PycharmProjects/mianxianduixiang/property的用法.py", line 51, in <module>
print(b1.name)
File "C:/Users/dsy/PycharmProjects/mianxianduixiang/property的用法.py", line 33, in name
return self.__name
AttributeError: 'BIM' object has no attribute '_BIM__name'
2.staticmethod:作用是专门给类用的,对对象而言是解除绑定
import time class Date: def __init__(self,year,month,day): self.year = year self.month = month self.day = day @staticmethod #专门给类用的 def now(): t = time.localtime() return Date(t.tm_year,t.tm_mon,t.tm_mday) @staticmethod def tomorrow(): t =time.localtime(time.time() + 86400) return Date(t.tm_year,t.tm_mon,t.tm_mday) date_now = Date.now() print(date_now.year,date_now.month,date_now.day) date_tom = Date.tomorrow() print(date_tom.year,date_tom.month,date_tom.day) >>>>>> 2017 12 11 2017 12 12
3.classmethod:把一个方法绑定给类,自动会把类传入,与对象的绑定方法机制相同
__str__的用法:定义在类内部的必须返回字符串类型,在打印由这个类产生的对象时会触发__str__的运行
class people: def __init__(self,name,age): self.name = name self.age = age def __str__(self):#定义在类内部必须返回字符串类型,在打印由这个类产生的对象时会触发__str__的运行 return '<对象的名字是:%s 对象的年龄是:%s>' % (self.name,self.age) p1 = people('deng',123) print(p1) >>>>>> <对象的名字是:deng 对象的年龄是:123>
classmethod的用法:
class FOO: def bar(self): print('>>>>>>') @classmethod #把一个绑定方法绑定给类,自动会把类传入给test的第一个变量 def test(cls,x): print(cls,x) f = FOO() print(f.bar) print(f.test)#对象和类都可以调用该方法,调用的是绑定给类的方法 print(FOO.test) f.test(1111) >>>>>> <bound method FOO.bar of <__main__.FOO object at 0x000002D8F2160DA0>> <bound method FOO.test of <class '__main__.FOO'>> <bound method FOO.test of <class '__main__.FOO'>> <class '__main__.FOO'> 1111
#######classmethod与__str__的混合使用:
import time class Date: def __init__(self,year,month,day): self.year = year self.month = month self.day = day @classmethod def now(cls): t = time.localtime() return cls(t.tm_year,t.tm_mon,t.tm_mday) @classmethod def tomorrow(cls): t =time.localtime(time.time() + 86400) return cls(t.tm_year,t.tm_mon,t.tm_mday) class EuroDate(Date): def __str__(self): return '年%s:月%s:日%s:' % (self.year,self.month,self.day) e1 = EuroDate.now() print(e1) >>>>>>>> 年2017:月12:日12:
补充:绑定方法与非绑定方法
一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2. 绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
二:非绑定方法:用staticmethod装饰器装饰的方法
1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说