面向对象进阶
一,.接口类
1. 继承有两种用途
1>:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
2>:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口
名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
# 一:这样不好,我要统一一下支付的规则. class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) a = Alipay() a.pay(100) b = QQpay() b.pay(200) # 二,统一支付的规则 归一化设计,统一 pay接口 class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) def pay(obj,money): obj.pay(money) #统一调用上面类中pay方法, a = Alipay() b = QQpay() pay(a,100) pay(b,200) # 三,但是,来了一个野生程序员,他不知道你的约定俗成的规则,就会出问题 class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) class Wechatpay: def fuqian(self,money): print('使用微信支付%s元' % money) def pay(obj,money): obj.pay(money) a = Alipay() b = QQpay() pay(a,100) pay(b,200) c = Wechatpay() c.fuqian(300) # 四,解决方式 # 定义一个父类,什么都不写,只是要求继承我的所有类有一个pay方法,这样就制定了一个规范,这就叫做接口类,后者抽象类. class Payment: def pay(self):pass class QQpay(Payment): def pay(self,money): print('使用qq支付%s元' % money) class Alipay(Payment): def pay(self,money): print('使用阿里支付%s元' % money) class Wechatpay(Payment): def fuqian(self,money): print('使用微信支付%s元' % money) def pay(obj,money): obj.pay(money) a = Alipay() b = QQpay() pay(a,100) pay(b,200) c = Wechatpay() c.fuqian(300) #五,他还是不知道看你这些都继承了一个类,所以你要制定一个规范,强制他执行. # 创建一个规范 from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass指定的是一个元类 @abstractmethod def pay(self):pass # 抽象方法 class Alipay(Payment): def pay(self,money): print('使用支付宝支付了%s元'%money) class QQpay(Payment): def pay(self,money): print('使用qq支付了%s元'%money) class Wechatpay(Payment): # def pay(self,money): # print('使用微信支付了%s元'%money) def recharge(self):pass def pay(a,money): a.pay(money) a = Alipay() a.pay(100) pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能 q = QQpay() q.pay(100) pay(q,100) w = Wechatpay() pay(w,100) # 到用的时候才会报错 # 抽象类和接口类做的事情 :建立规范 # 制定一个类的metaclass是ABCMeta, # 那么这个类就变成了一个抽象类(接口类) # 这个类的主要功能就是建立一个规范
2. 实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基
类出现强耦合。
1> 继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外
部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在
程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好
象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、
网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后
做出针对性的设计:细致到什么程度,视需求而定)。
2> 注意:在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像
接口,其实并没有起到接口的作用,子类完全可以不用去实现接口
3> 接口提取了一群类共同的函数,可以把接口当做一个函数的集合。 然后让子
类去实现接口中的函数。 这么做的意义在于归一化,什么叫归一化,就是只
要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用
法上来说都一样。 归一化,让使用者无需关心对象的类是什么,只需要的知道
这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。 比
如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老
鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠
和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会
跑,都会吃,都能呼吸。 再比如:我们有一个汽车接口,里面定义了汽车所有
的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽
车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是
奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作
手法(函数调用)都一样
二, 抽象类
1. 什么是抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊
的类,它的特殊之处在于只能被继承,不能被实例化
2. 为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同
的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这
个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子,你永远
无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被
实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不
同的.
2. 抽象类与接口类
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属
性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以
用来实现归一化设计
3. 在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有
一些基本的概念。
4. 多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
5. 接口隔离原则: 使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
6. 方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
三, 多态
1. Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚
“鸭子类型”
class F1: pass class S1(F1): def show(self): print 'S1.show' class S2(F1): def show(self): print 'S2.show' # 由于在Java或C#中定义函数参数时,必须指定参数的类型 # 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类 # 而实际传入的参数是:S1对象和S2对象 def Func(F1 obj): """Func函数需要接收一个F1类型或者F1子类的类型""" print obj.show() s1_obj = S1() Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show s2_obj = S2() Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show Python伪代码实现Java或C#的多态 Python伪代码实现java C#多态
#鸭子类型
class F1: pass class S1(F1): def show(self): print 'S1.show' class S2(F1): def show(self): print 'S2.show' def Func(obj): print obj.show() s1_obj = S1() Func(s1_obj) s2_obj = S2() Func(s2_obj)
#都有同样的方法,使用上类似,具有统一化设计思想,就叫鸭子类型
四, 封装
1. 封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
-
- 将内容封装到某处
- 从某处调用被封装的内容
2. 将内容封装到某处
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,
在内存里类似于下图来保存。
3. 第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
-
- 通过对象直接调用
- 通过self间接调用
1> 通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封
装的内容:对象.属性名
class Foo: def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('wupeiqi', 18) print obj1.name # 直接调用obj1对象的name属性 print obj1.age # 直接调用obj1对象的age属性 obj2 = Foo('alex', 73) print obj2.name # 直接调用obj2对象的name属性 print obj2.age # 直接调用obj2对象的age属性
通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print self.name print self.age obj1 = Foo('wupeiqi', 18) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo('alex', 73) obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78
4. 综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象
中,然后通过对象直接或者self间接获取被封装的内容
五, 封装性与扩展性
1. 封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的
代码;而外部使用者只知道一个接口(函数),只要接(函数)名、参数不变,使用者的
代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接这个基础约定
不变,则代码改变不足为虑.
#类的设计者 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()
六, 面向对象结构分析
1. 如下面的图所示:面向对象整体大致分两块区域:
那么每个大区域又可以分为多个小部分
class A: company_name = '老男孩教育' # 静态变量(静态字段) __iphone = '1353333xxxx' # 私有静态变量(私有静态字段) def __init__(self,name,age): #普通方法(构造方法) self.name = name #对象属性(普通字段) self.__age = age # 私有对象属性(私有普通字段) def func1(self): # 普通方法 pass def __func(self): #私有方法 print(666) @classmethod # 类方法 def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法') @staticmethod #静态方法 def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法') @property # 属性 def prop(self): pass
2. 面向对象的公有私有
1> 对于每一个类的成员而言都有两种形式:
-
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
2> 私有成员和公有成员的访问限制不同:
静态字段(静态变量)
-
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问;
class C: name = "公有静态字段" def func(self): print C.name class D(C): def show(self): print C.name C.name # 类访问 obj = C() obj.func() # 类内部可以访问 obj_son = D() obj_son.show() # 派生类中可以访问
class C: __name = "私有静态字段" def func(self): print C.__name class D(C): def show(self): print C.__name C.__name # 不可在外部访问 obj = C() obj.__name # 不可在外部访问 obj.func() # 类内部可以访问 obj_son = D() obj_son.show() #不可在派生类中可以访问
3. 属性
1> 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('egon',75,1.85) print(p1.bmi)
2> 为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name
是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方
法定义为对同一个属性:获取、修改、删除
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property #将该方法伪装成一个属性,在代码级别上没有本质的提升,但是让其看起来很合理. def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter #固定写法, 其中伪装的函数属性名,要修改的方法属性名和 @属性名.setter 要一致 def price(self, value): self.original_price = value @price.deltter def price(self, value): del self.original_price obj = Goods() obj.price # 获取商品价格 obj.price = 200 # 修改商品原价 del obj.price # 删除商品原价
4. 类方法: @classmethod
应用场景:
(1), 类中有些方法是不需要传入对象,不要对象的一切东西
(2), 对类中的静态变量进行改变
(3), 继承中,父类得到子类的空间(子类类名调用,类名传给cls,父类中则会得到子类地址)
class A: def func(self):# 普通方法 print(self) @classmethod #类方法 def func1(cls) print(cls) a = A() a.func() #对象调用的普通方法 A.func(a) #类名调用的普通方法 A.func1() #类方法:通过类名调用的方法,类方法中第一个参数约定俗成是 cls #python自动将类名(类空间)传给 cls
注意:对象调用类方法,cls 得到的是类本身
5. 静态方法: @staticmethod
优点: 1. 代码块清晰 2. 增加复用性
class A : @staticmethod def func(self): print(33) A.func() #结果: 33
七, isinstance与issubclass
isinstance(obj,cls) 检查obj是否是类 cls 的对象
class A: pass class B(A): pass obj = B() print(isinstance(obj,B)) #True 判断对象所属关系,包含所有继承关系,只要是 print(isinstance(obj,str)) #True
print(type(obj) is str) #不包含继承系,只管一层
issubclass(sub, super)检查sub类是否是 super 类的派生类
class A: pass class B(A): pass abj = B() print(issubclass(B,A)) #True #判断类的继承关系
八,反射
1. python面向对象中的反射:通过字符串数据类型的变量名来访问这个变量的值。python
中的一切事物都是对象(都可以使用反射)
2. 使用对象来反射:
class Foo: f = '类的静态变量' def __init__(self,name,age): self.name=name self.age=age def say_hi(self): print('hi,%s'%self.name) obj=Foo('egon',73) #检测是否含有某属性 print(hasattr(obj,'name')) print(hasattr(obj,'say_hi')) #获取属性 n=getattr(obj,'name') print(n) func=getattr(obj,'say_hi') func() print(getattr(obj,'aaaaaaaa','不存在啊')) #报错 #设置属性 setattr(obj,'sb',True) setattr(obj,'show_name',lambda self:self.name+'sb') print(obj.__dict__) print(obj.show_name(obj)) #删除属性 delattr(obj,'age') delattr(obj,'show_name') delattr(obj,'show_name111')#不存在,则报错 print(obj.__dict__)
3. 实用类来反射:
class Foo(object): staticField = "old boy" def __init__(self): self.name = 'wupeiqi' def func(self): return 'func' @staticmethod def bar(): return 'bar' print getattr(Foo, 'staticField') print getattr(Foo, 'func') print getattr(Foo, 'bar')
4. 对当前模块反射
import sys def s1(): print 's1' def s2(): print 's2' this_module = sys.modules[__name__] hasattr(this_module, 's1') getattr(this_module, 's2')
5. 对其他模块反射
import sys def s1(): print 's1' def s2(): print 's2' this_module = sys.modules[__name__] hasattr(this_module, 's1') getattr(this_module, 's2')
九, 内置方法(双下划线方法,魔术方法,类中的特殊方法)
__名字__ 类中的每一个双下划线方法都有他自己的特殊意义
内置函数和内置方法是有密切联系的
所有的内置方法不需要再外部直接调用,而是用一些其它的内置函数或特殊的语法来自动
触发这些内置方法
1. __call__ 对象后面加括号,触发执行。
注:__init__方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的
执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__
2 .2. __len__
class A: def __init__(self): self.a = 1 self.b = 2 def __len__(self): return len(self.__dict__) #__len__方法return的值就是len函数的返回值 a = A() print(len(a)) #len(obj)相当于调用了这个对象的__len__方法,如果obj对象没有__len__方法
那么len函数就会报错
3.__new__
构造方法 :__new__
初始化方法: __init__
1> 面向对象在实例化时:
1.开辟一个空间,属于对象的 2. 把对象空间传给self,执行init 3. 将这个对象空间返回
给调用者
2> __new__开辟空间,返回给self, 在实例化对象之后,__init__之前,执行__new__方法.
class SIngle: def __new__(cls,*args,**kwargs): print('在new方法里') obj=object.__new__(cls) print('在new方法里',obj) return obj def __init__(self): print('在init方法里',self) obj=SIngle()
3> 单例类
如果一个类,从头到尾只能有一个实例,那么这个类就是一个单例类
class A: __instance = None def __new__(cls, *args, **kwargs): if not cls.__instance: cls.__instance=object.__new__(cls) return cls.__instance def __init__(self): self.name=name self.age=age s1=A('he',23) s2=A('ha',30) print(s1.name) print(s2.name) #结果: ha
4. __str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值.
class A: def __init__(self): pass def __str__(self): return '白' a = A() print(a) #相当于调用a的__str__方法 print('%s' % a)#相当于执行a.__str__方法
另:print(str(obj)) #内置的数据类型相当于执行obj.__str__方法
5. __repr__ :
1> __repr__ 是__str __的备胎,如果有__str__方法,那么 print, %s, str 都去执行str方法,
并且使用str的返回值;如果没有__str__,那么print, %s, str都会执行repr
repr(obj) %r
2> 在子类中使用__str__,先找子类的__str__,没有的话要向上找,只要父类不是object,就
执行父类的__str__,但是如果出了object之外的父类都没有__str__方法,就执行子类的
__repr__方法,如果子类也没有,还要向上继续找父类中的__repr__方法,一直找不到
再执行object类中的__str__的方法
class A: def __init__(self,name): self.name=name def __str__(self): return '**%s**' % self.name def __repr__(self): return self.name a = A('alex') print(a) print(str(a),repr(a)) print('% | %r' % (a,a)) #注意: # %r : 把 repr 的返回结果给 %r; # %s :把 str 的返回结果给 %s
6. getattr(object, name[default])
获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值
可选。需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行
这个方法,可以在后面添加一对括号。
>>> class test(): ... name="xiaohua" ... def run(self): ... return "HelloWord" ... >>> t=test() >>> getattr(t, "name") #获取name属性,存在就打印出来。 'xiaohua' >>> getattr(t, "run") #获取run方法,存在就打印出方法的内存地址。 <bound method test.run of <__main__.test instance at 0x0269C878>> >>> getattr(t, "run")() #获取run方法,后面加括号可以将这个方法运行。 'HelloWord' >>> getattr(t, "age") #获取一个不存在的属性。 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: test instance has no attribute 'age' >>> getattr(t, "age","18") #若属性不存在,返回一个默认值。 '18' >>>
7. setattr(object, name, values)
给对象的属性赋值,若属性不存在,先创建再赋值.
>>> class test(): ... name="xiaohua" ... def run(self): ... return "HelloWord" ... >>> t=test() >>> hasattr(t, "age") #判断属性是否存在 False >>> setattr(t, "age", "18") #为属相赋值,并没有返回值 >>> hasattr(t, "age") #属性存在了 True >>>
8. item 系列: 和对象使用 [ ] 访问值有联系
__getitem__
class B:
#def __init__(self,lis):
#self.lis = lis def __getitem__(self,item): return getattr(self,item)
#return self.lis[litem]
def __setitem__(self,key,value):
setattr(self,key,value)
#self.lis[key] = vslue
def __delitem__(self,key):
delattr(self,key)
#self.lis.pop(key)
b = B() b['k'] = 'v1' #触发__setitem__ print(b['k1']) # __getitem__
del b['k1'] # __delitem__
#b = B(['11','22','33','44'])
#print(b.lis[0])
#B[3] = '99'
#print(b.list)
#del b[2]
#print(b.lis)
9. hash方法
底层数据结构基于hash值寻址的优化操作
hash是一个算法能够把某一个要存在内存里的值通过一系列计算,保证不同值hash
值的结果不一样.
对通一个值多次执行的python代码的时候hash值的结果是不同的,但是对通一个值
再痛一次执行python代码的时候hash值永远不变.
class Employee: def __init__(self,name,age,sex,partment) self.name=name self.age=age self.sex = sex self.partment = partment def__hash__(self): #筛选hash值相等 return hash('%s%s' % (self.name,self.sex)) def __eq__(self,other): #筛选值相等 if self.name == other.name and self.sex == other.sex return True employ_lst = [...] for i in range(200) employ_lst.append(Employee('alex',i,'male','python')) for i in range(200) employ_lst.append(Employee('wusir',i,'male','python')) for i in range(200) employ_lst.append(Employee('taibai',i,'male','python')) employ_lst = set(employ_lst) for person in employ set : print(person.__dic__ ) #set集合的去重原理
10. eq 方法
class A : def __init__(self,name,age): self.name = name self.age = age def __eq__(self,other): if self.name == other.name and self.age == other.age: return True a = A('小白',88) aa = A('大白'99) aaa = A('超大白',100) print(a,aa) print(a == aa == aaa) # == 这个语法是完全和__eq__相关
11. 析构方法
释放一个空间
归还/释放一些在创建对象的时候借用的一些资源
class A : def __del__(self): #析构方法 del A的对象 会自动触发这个方法 print('饿了吗') a = A() del a #对象删除 del 当不主动进行该操作时,python解释器在程序执行 #完后也会自动触发 __del__ 函数(垃圾回收机制). print(a)
上一篇: H5实现魔方游戏