面向对象
面向过程与面向对象的对比:
面向过程:核心是过程二字,过程指的是问题的解决步骤,即先干什么再干什么,基于面向过程去设计程序就好比在设计一条流水线,是一种机械式的思维方式
优点:复杂的问题流程化,进而简单化
缺点:可扩展性差
应用:脚本程序,比如linux系统管理脚本,著名案例:linux内核,httpd,git
面向对象:核心是对象二字,对象就是特征与技能的结合体,如果把设计程序比喻成
创造一个世界,那你就是这个世界的上帝,与面向过程对机械流水的模拟形式鲜明的对比,面向对象更加注重的对现实时间的模拟。
优点:可扩展性,对某一个对象单独修改,会立刻反映到整个体系中
缺点:
1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,只有对象之间交互才能准确地知道最终的结果
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
一.类与对象
对象是特征与技能的结合体
类是一系列对象相似的特征与技能的结合体
类有两种属性:数据属性和函数属性
1. 类的数据属性是所有对象共享的
2. 类的函数属性是绑定给对象用的
对象调用类的函数中的变量时,首先会从该函数的命名空间寻找变量,找不到则去类中找,再去父类中找,最后都找不到则抛出异常
函数命名空间 ---> 类的数据属性 ---> 父类
Python中为类内置的特殊属性
类名.__name__ # 类的名字(字符串)
类名.__doc__ # 类的文档字符串
类名.__base__ # 类的第一个父类
类名.__bases__ # 类所有父类构成的元组
类名.__dict__ # 类的字典属性
类名.__module__ # 类定义所在的模块
类名.__class__ # 实例对应的类(仅新式类中)
在程序中:
一定是先定义类,后调用类来产生对象
class OldboyStudent: # 定义类 school = 'oldboy' #类的数据属性
def learn(self): #类的函数属性
print('is learning')
def eat(self):
print('is eating')
print('======>')
# 类体的代码在类定义阶段就会执行,理所应当会产生类的名称空间,用__dict__属性查看
print(OldboyStudent.__dict__)
print(OldboyStudent.__dict__['school'])
print(OldboyStudent.__dict__['learn'])
#产生程序中的对象:类名加括号,调用类,产生一个该类的实际存在的对象,该调用过程称为实例化,产生的结果又可以成为实例
class OldboyStudent:
school = 'oldboy'
def __init__(self,name,age,sex): #在实例化时,产生对象之后执行
# if not isinstance(name,str): # 判断如果name不是字符串类型
# raise TypeError # raise主动抛出异常
self.name=name
self.age=age
self.sex=sex
# return None #__init__方法必须返回None
# 绑定方法:绑定给谁,就由谁来调用,谁来调用就把“谁”本身当做第一个参数传入,所以在类中定义函数,函数中的参数中必须要传入一个self参数
def learn(self):
print('is learning')
def eat(self):
print('is eating')
obj1=OldboyStudent('李大炮',18,'女') #
#分两步:
#第一步:先产生一个空对象obj1
#第二步:OldboyStudent.__init__(obj1,'李大炮',18,'女')
print(obj1.__dict__)
obj2=OldboyStudent('张全蛋',28,'男')
obj3=OldboyStudent('牛榴弹',18,'女')
练习:
class Garen: #定义盖伦的类 camp='Demacia' # 定义盖伦的数据属性 def __init__(self,nickname,life_value=100,aggresivity=80): self.nickname=nickname self.life_value=life_value self.aggresivity=aggresivity def attack(self,enemy): # 攻击函数 enemy.life_value-=self.aggresivity #被攻击对象的生命值减去己方的攻击力class Riven: # 瑞雯的类 camp = 'Noxus' def __init__(self, nickname, life_value=80, aggresivity=100): self.nickname = nickname self.life_value = life_value self.aggresivity = aggresivity def attack(self, enemy): enemy.life_value -= self.aggresivity# 实例化两个对象g1=Garen('草丛猥琐男')r1=Riven('兔女郎') print(r1.life_value)g1.attack(r1)print(r1.life_value)
二.继承
继承指的是类与类之间的关系,是一种什么是什么的关系
1 继承的功能之一:解决类与类之间的代码重复问题
2 继承是类与类之间的关系,是一种,什么是什么的关系
3 在子类派生出的新的属性,以自己的为准
4 在子类派生出的新的方法内重用父类的功能的方式:
指名道姓法 OldboyPeople.__init__ 这种调用方式本身与继承是没有关系
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
#继承的基本形式class ParentClass1: #定义父类 passclass ParentClass2: #定义父类 passclass SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass passclass SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__bases__(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
__base__只查看从左到右继承的第一个子类
__bases__则是查看所有继承的父类
多继承的数据查找:
经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
继承原理:(python如何实现的继承)
对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__[<class '__main__.F'>, <class'__main__.D'>, <class '__main__.B'>, <class '__main__.E'>,<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
子类中调用父类的方法:
方法一:指名道姓法,即父类名.父类方法()
#_*_coding:utf-8_*___author__ = 'Linhaifeng' class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self)line13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()
方法二:super()
super会根据mro表进行查找,即使没有直接继承关系
class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜单车 passline13=Subway('中国地铁','180m/s','1000人/箱','电',13)line13.run()
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
继承与抽象
先抽象后继承
抽象就是抽取类似或者比较像的部分,划分类别,抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
三.组合
在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合与继承都是有效地利用已有类的资源的重要方式
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
class OldboyPeople: # 定义老男孩人这个基类 school = 'oldboy' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def eat(self): print('is eating')class OldboyStudent(OldboyPeople): #老男孩学生类,继承OldboyPeople类 def __init__(self,name,age,sex): OldboyPeople.__init__(self,name,age,sex) self.course=[] def learn(self): print('%s is learning' %self.name)class OldboyTeacher(OldboyPeople): def __init__(self,name,age,sex,salary,title): OldboyPeople.__init__(self,name,age,sex) self.salary=salary self.title=title self.course=[] def teach(self): print('%s is teaching' %self.name)class Course: def __init__(self,course_name,course_period,course_price): self.course_name=course_name self.course_period=course_period self.course_price=course_price def tell_info(self): print('<课程名:%s 周期:%s 价格:%s>' %(self.course_name,self.course_period,self.course_price))python=Course('Python','6mons',3000) # 实例化课程linux=Course('Lnux','3mons',2000)bigdata=Course('BigData','1mons',1000)# 实例化老师egon_obj=OldboyTeacher('egon',18,'male',3.1,'沙河霸道金牌讲师') egon_obj.course.append(python) # 为老师添加课程到课程列表egon_obj.course.append(linux) for obj in egon_obj.course: # 循环打印egon老师的课程列表 obj.tell_info() yl_obj=OldboyStudent('yanglei',28,'female') # 实例化学生yl_obj.course.append(python) # 为学生添加课程到课程列表for i in yl_obj.course: i.tell_info()
四.绑定方法与非绑定方法
类中定义的函数分为两大类:
绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
1. 绑定到类的方法:用classmethod装饰器装饰的方法,为类量身定制
类.boud_method() # 自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
2. 绑定到对象的方法:没有被任何装饰器装饰的方法,为对象量身定制
对象.boud_method() # 自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
练习:
setting.py文件内容:
HOST='192.168.31.1'PORT=3106
主程序:
import settingsclass MySql: def __init__(self,host,port): self.host=host self.port=port @classmethod # 把classmethod函数当做装饰器 def from_conf(cls): # 使用装饰器后创建函数后会自动传入cls参数 return cls(settings.HOST,settings.PORT)conn1=MySql('127.0.0.1',3306) # 使用普通方法调用类生成一个对象conn2=MySql.from_conf() # 调用含有装饰器的函数来生成一个对象,此函数会把类当做第一个参数(cls)传入print(conn1.host,conn2.host)
非绑定方法(用staticmethod装饰器装饰的方法)
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说,就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
statimethod不与类或对象绑定,谁都可以调用,没有自动传值效果
练习:
setting.py文件内容:
HOST='192.168.31.1'PORT=3106
主程序:
import settingsimport uuidclass MySql: def __init__(self,host,port): self.host=host self.port=port self.id=self.create_id() # @classmethod # def from_conf(cls): #绑定给类的 # print(cls) # # return cls(settings.HOST,settings.PORT) # # def func1(self): #绑定给对象的 # pass @staticmethod def create_id(): #非绑定方法,不跟任何绑定 return str(uuid.uuid1())conn1=MySql('127.0.0.1',3306)conn2=MySql('127.0.0.2',3306)conn3=MySql('127.0.0.3',3306)print(conn1.id,conn2.id,conn3.id)
五、接口与归一化设计
接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数
归一化,就是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样
好处:
a. 归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
b. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
在python中根本就没有一个叫做interface的关键字,如果要去模仿接口的概念
a.可以借助第三方模块:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
b.使用继承
继承的两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write passclass Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的写方法')class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的写方法')class Process(Interface): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的写方法')# 实例化三个对象t=Txt()s=Sata()p=Process()t.read()s.read()p.read() 输出:文本数据的读取方法硬盘数据的读取方法进程数据的读取方法
六、抽象类
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
import abcclass Interface(metaclass=abc.ABCMeta):#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 all_type='file' @abc.abstractmethod def read(self): #定接口函数read pass @abc.abstractmethod def write(self): #定义接口函数write passclass Txt(Interface): #文本,具体实现read和write def read(self): pass def write(self): passt=Txt()print(t.all_type)
七、多态和多态性
一种接口,多种实现
允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了――代码重用
而多态则是为了实现另一个目的――接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用
八、封装
把类内部的变量名或者函数名前面加上__,在类外部就无法调用,在类内部能调用
class Foo: __N=111111 #_Foo__N 在语法检测阶段会自动转换 def __init__(self,name): self.__Name=name #self._Foo__Name=name def __f1(self): #_Foo__f1 print('f1') def f2(self): self.__f1() #self._Foo__f1()f=Foo('egon') #实例化一个对象print(f.__N) #无法调用f.__f1() #无法调用f.__Name #无法调用f.f2() #属于类内部的调用,返回结果f1 如果要访问使用这种隐藏方法的类的隐藏属性,可以使用下面的调用方法:print(f. _Foo __N)f._Foo __f1f._Foo __Name
这种隐藏方法特点:
a.只是一种语法上变形操作,并不会将属性真正隐藏起来
b. 这种语法级别的变形,是在类定义阶段发生的,并且只在类定义阶段发生
Foo.__x=123123123123123123123123123123123123123123 #在类外部进行定义变量print(Foo.__dict__) # 可以检测到属性print(Foo.__x) # 可以调用,说明在此隐藏方法只在定义时发生,类外部无效f.__x=123123123print(f.__dict__)print(f.__x)
c. 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的
class Foo: def __f1(self): #在定义阶段变形成了_Foo__f1 print('Foo.f1') def f2(self): self.__f1() #self._Foo_f1class Bar(Foo): def __f1(self): #在这一步实际变形成了_Bar__f1,不会覆盖父类中的__f1 print('Bar.f1')# b=Bar()# b.f2()
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
在Python中封装不是单纯意义上的隐藏
a.封装数据属性
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制
class People: def __init__(self,name,age): if not isinstance(name,str): raise TypeError('%s must be str' %name) if not isinstance(age,int): raise TypeError('%s must be int' %age) self.__Name=name #隐藏数据属性 self.__Age=age def tell_info(self): #把隐藏属性放在函数中,调用时直接调用函数 print('<名字:%s 年龄:%s>' %(self.__Name,self.__Age)) def set_info(self,x,y): if not isinstance(x,str): raise TypeError('%s must be str' %x) if not isinstance(y,int): raise TypeError('%s must be int' %y) self.__Name=x self.__Age=yp=People('egon',18)p.tell_info() #调用查看接口,可以调用隐藏的属性p.set_info('Egon',19)p.tell_info() #调用修改接口,可以调用隐藏的属性
b.封装函数属性
为了隔离复杂度
例如:
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
隔离了复杂度,同时也提升了安全性
class ATM: def __card(self): # 把用户不需要知道的功能隐藏起来 print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): #只提供取款的接口,调用隐藏的属性 self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money()a=ATM()a.withdraw() #用户在取款时只需要调用取款的接口就行了
九、静态属性(property)
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
'''例:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)成人的BMI数值:过轻:低于18.5正常:18.5-23.9过重:24-27肥胖:28-32非常肥胖, 高于32 体质指数(BMI)=体重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86'''class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property #在调用bmi()函数方法时可以把bmi当做属性来调用 def bmi(self): return self.weight / (self.height**2)p=People('egon',75,1.80)print(p.bmi) # 在调用时不用加()运行
访问、设置、删除
class Foo: def __init__(self,x): self.__Name=x @property def name(self): return self.__Name @name.setter #前提是name已经被property修饰过至少一次,才能调setter def name(self,val): if not isinstance(val,str): raise TypeError self.__Name=val @name.deleter def name(self): # print('=-====>') # del self.__Name raise PermissionErrorf=Foo('egon')print(f.name) f.name='Egon' #修改操作,调用name.setter修饰过的name函数print(f.name) del f.name #删除操作,调用name.deleter修饰的name函数print(f.name)
本文出自 “lyndon” 博客,请务必保留此出处http://lyndon.blog.51cto.com/11474010/1961392