面向对象编程
面向对象核心就是对象二字,对象就是特征与技能的结合体
优点:可扩展性强
缺点:编程复杂程度高
应用于用户需求经常变化,互联网应用,游戏,企业内部应用
面向对象3大特征
-
Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
-
Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
-
Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
类
类就是一系列对象相似的特征与技能的结合体,站在不同角度,得到的分类是不一样的
在现实世界中:一定是现有对象,后有类
在程序中:一定先定义类,后调用类产生对象
<!--类在定义的时候,内部代码已经运行-->
类的增删改查
# 查 print(LuffyStudent.school) # LuffyStudent.__dict__['school'] print(LuffyStudent.learn) # LuffyStudent.__dict__['learn'] # 增 LuffyStudent.county='China' # print(LuffyStudent.__dict__) print(LuffyStudent.county) # 删 del LuffyStudent.county # 改 LuffyStudent.school='Luffycity'
类的方法
-
构造方法:实例化时给实例一些初始化参数,或执行一些其它的初始化工作
-
普通方法:定义类的一些正常功能
-
析构方法:实例在内存中被删除时,会自动执行这个方法,如你在内存里生成了一个人的实例,现在他被打死了,那这个人除了自己的实例要被删除外,可能它在实例外产生的一些痕迹也要清除掉,清除的动作就可以写在这个方法里
class Person(object): def __init__(self, name, age): # 构造方法 self.name = name # 数据属性 self.age = age def talk(self): # 普通方法,函数属性(没有被任何装饰器装饰的) print("Hello, my name is %s, I'm %s years old!" % (self.name, self.age)) def __del__(self): # 析构方法 print("running del method, this person must be died.") p = Person("q1ang", 22) p.talk() del p print('--end program--')
属性查找
类有两种属性:数据属性和函数属性
-
类的数据属性是所有对象共享的
-
类的函数数据是绑定给对象用的,称为绑定到对象的方法
绑定方法
class Student: def __init__(self, name, age, sex): self.name=name self.age=age self.sex=sex def learn(self): print('%s is learning' % self.name) # 新增self.name def eat(self): print('%s is eating' % self.name) def sleep(self): print('%s is sleeping' % self.name) s1 = Student('q1ang','男',18) s1.learn() # 绑定方法,
继承
继承指的是类与类之间的关系,是一种创建新类的方式 ,在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类
class ParentClass1: # 定义父类 pass class ParentClass2: # 定义父类 pass class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): # python支持多继承,用逗号分隔开多个继承的类 pass
继承的实现原理
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列表并遵循如下三条准则:
-
子类会先于父类被检查
-
多个父类会根据它们在列表中的顺序被检查
-
如果对下一个类存在两个合法的选择,选择第一个父类
如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先
-
python2 中
class A(): # 为经典类,采用深度优先 pass class A(object): # 继承了objec类,为新式类,采用广度优先 pass
-
python3 中统一为新式类,采用广度优先
在子类中调用父类的方法
-
方式一:指名道姓,即父类名.父类方法()
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()
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): # 摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run()
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找
# A没有继承B,但是A内super会基于C.mro()继续往后找 class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() # 打印结果:from B print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
组合与重用性
组合是除了继承之外的另外一种软件重用的方式
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
>>> class Equip: #武器装备类 ... def fire(self): ... print('release Fire skill') ... >>> class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 ... camp='Noxus' ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 ... >>> r1=Riven('锐雯雯') >>> r1.equip.fire() #可以使用组合的类产生的对象所持有的方法 release Fire skill
-
继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
-
组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
接口与归一化设计
接口
自己提供给使用者来调用自己功能的方式\方法\入口
为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
-
归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
-
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
-
就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
-
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
-
模仿interface
-
继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
-
声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Interface: # 定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): # 定接口函数read pass def write(self): # 定义接口函数write pass class 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('进程数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类
抽象类
-
什么是抽象类与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
-
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的
-
在python中实现抽象类
# 一切皆文件 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)
-
抽象类与接口 抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
多态与多态性
多态指的是一类事物有多种形态
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')
多态动态绑定(多态性)
多态性是指在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
peo=People() dog=Dog() pig=Pig() # peo、dog、pig都是动物,只要是动物肯定有talk方法 # 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用 peo.talk() dog.talk() pig.talk() # 更进一步,我们可以定义一个统一的接口来使用 def func(obj): obj.talk()
-
增加了程序的灵活性以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
-
增加了程序额可扩展性通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
鸭子类型
创建一个外观和行为像,但与它无任何关系的全新对象
# 二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
封装
# 封装仅仅这是一种变形操作
# 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A: __N=0 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 # 变形为self._A__X def __foo(self): # 变形为_A__foo print('from A') def bar(self): self.__foo() # 只有在类内部才可以通过__foo的形式访问到. # A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
-
类中定义的
__x
只能在内部使用,如self.__x
,引用的就是变形的结果。 -
这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
-
在子类定义的
__x
不会覆盖在父类定义的__x
,因为子类中变形成了:_子类名__x
,而父类中变形成了:_父类名__x
,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
-
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:
_类名__属性
,然后就可以访问了,如a._A__N
-
变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
-
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
封装不是单纯意义的隐藏
-
封装数据将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
class Teacher: def __init__(self,name,age): self.__name=name self.__age=age def tell_info(self): print('姓名:%s,年龄:%s' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必须是字符串类型') if not isinstance(age,int): raise TypeError('年龄必须是整型') self.__name=name self.__age=age t=Teacher('q1ang',18) t.tell_info() t.set_info('q1ang',19) t.tell_info()
-
封装方法:目的是隔离复杂度
# 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 # 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 # 隔离了复杂度,同时也提升了安全性 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 def bmi(self): return self.weight / (self.height**2) p1=People('q1ang',75,1.85) print(p1.bmi) # bmi变成了p1的一个数据属性,但是该属性不能被赋值
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
需要对这种属性进行赋值,删除时:
class Foo: def __init__(self,val): self.__NAME=val # 将所有的数据属性都隐藏起来 @property def name(self): return self.__NAME # obj.name访问的是self.__NAME(这也是真实值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): # 在设定值之前进行类型检查 raise TypeError('%s must be str' %value) self.__NAME=value # 通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 # 抛出异常'TypeError: 10 must be str' del f.name # 抛出异常'TypeError: Can not delete'
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
# 类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length # 使用者 >>> r1=Room('卧室','egon',20,20,20) >>> r1.tell_area() # 使用者调用接口tell_area # 类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): # 对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high # 对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 >>> r1.tell_area()
绑定方法与非绑定方法
绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
-
绑定到类的方法:用
@classmethod
装饰器装饰的方法。为类量身定制 类.boud_method(),自动将类当作第一个参数传入 (其实对象也可调用,但仍将类当作第一个参数传入)
-
绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制 对象.boud_method(),自动将对象当作第一个参数传入 (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
非绑定方法:用@staticmethod
装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
反射
通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
hasattr(object,name)
判断object中有没有一个name字符串对应的方法或属性
getattr(object, name, default=None)
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass
setattr(x, y, v)
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass
delattr(x, y)
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ pass