03 面向对象之封装
一、封装
1.1 概念
什么是:
1.当有一些数据不希望外界可以直接修改时,当有一些函数不希望给外界使用时,将不需要对外提供的内容都隐藏起来
2. 对外提供公共方法对其访问
优点:
1.保障关键数据的安全;
2. 将变化隔离,对外部隐藏实现细节,隔离复杂度,便于使用;(如将pc的启动项、加载项、检测项等封装到open下)
3. 提高复用性;
特点:
1.外界不能直接访问
2.内部依然可以使用
权限控制
在python只要两种权限,
1.公开的 默认就是公开的
2.私有的 只能由当前类自己使用
1.2 封装语法
封装有两种:
封装属性,私有化属性 属性前加杠杠
封装方法,私有化方法 方法前加杠杠
1.2.1 封装属性
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形后的结果
2.在外部是无法通过__x这个名字访问到的,只能通过接口访问
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的
4.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__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是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
class Student: def __init__(self,name,age): self.__name = name #封装属性 :杠杠 self.agehhh = age #点后面随便写,写什么调用时对应就行了 def get_name(self): #外界访问的接口 print(t.__name) t = Student(444,555) print(t.agehhh) #555 # print(t.name)#报错 外部不能访问封装的 # print(t.__name) #也报错 print(t._Student__name) #444 杠+类名+杠杠+name 这种是封装的本质 能调出来但是没意思啊 t.get_name() #444 通过接口访问,正规合法路经
1.2.2 封装方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from A
二、外部访问私有的内容
属性虽然被封装了,但是外界还是需要使用的,通过定义方法来完成外界对私有属性的修改和访问
这是一个下载器类,需要提供一个缓存大小这样的属性 缓存大小不能超过内存限制,不嗯能够让用户随便设置,因此我们对缓存进行封装, 但是有些用户电脑实在太水,需要比一般人更小的缓存,必须修改,我们可以定义方法也就是接口,给用户修改的机会 class Downloader: def __init__(self,filename,url,buffer_size): #文件名 链接 缓冲大小 self.filename = filename self.url = url self.__buffer_size= buffer_size def start_download(self): if self.__buffer_size <= 1024*1024: #内部随便访问私有属性 print("开始下载....") print("当前缓冲器大小",self.__buffer_size) else: print("内存炸了! ") def set_buffer_size(self,size): #给用户提供修改接口,并且在这里还可以进行某些额外限制,比不封装让用户直接改好多了 #可以在方法中添加额外的逻辑 if not type(size) == int: print("大哥 缓冲区大小必须是整型") else: print("缓冲区大小修改成功!") self.__buffer_size = size def get_buffer_size(self): return self.__buffer_size d = Downloader("葫芦娃","http://www.baicu.com",1024*1024) # 通过函数修改内部封装的属性 d.set_buffer_size(1024*512) # 通过函数访问内部封装的属性 print(d.get_buffer_size()) print(d.filename) d.start_download()
三、
通过方法来修改或访问属性,本身没什么问题,但是这给对象的使用者带来了麻烦.
使用必须知道哪些是普通属性,哪些是私有属性,需要使用不同的方式来调用他们
1.@property 该装器用在获取属性的方法上 2.@key.setter 该装器用在修改属性的方法上 3.@key.deleter 该装器用在删除属性的方法上 注意:key是被property装饰的方法的名称 也就是属性的名称 内部会创建一个对象 变量名称就是函数名称 所以在使用setter和deleter时 必须保证使用对象的名称去调用方法 所以是 key.setter
class A: def __init__(self,name,key): self.__name = name self.__key = key @property def key(self): print("111") return self.__key @key.setter def key(self,new_key): print("222") if new_key <= 100: self.__key = new_key else: print("key 必须小于等于100") @key.deleter def key(self): print("333") print("不允许删除该属性") del self.__key a = A("jack", 123) print(a.key) a.key = 21 del a.key
# property属性 (可以使用property来进行计算属性)
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值(不装饰返回的是一个属性,一大串字符)
将一个类的函数定义成特性以后,去调用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
#计算圆的周长和面积 import math class Circle: def __init__(self,radius): #圆的半径radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 print(c.perimeter) #同上 ''' 输出结果: 314.1592653589793 62.83185307179586 '''
class Square: def __init__(self,width): self.width = width # self.area = self.width * self.width 这样写在实例化对象时就定死了边长为10 @property def area(self): return self.width * self.width s = Square(10) print(s.area) #100 s.width = 20 print(s.area) #400
四、接口 抽象类 鸭子类型
1. 接口
接口是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码
接口本质是一套协议标准,遵循这个标准的对象就能被调用
例如电脑提前指定制定一套USB接口协议,只要你遵循该协议,你的设备就可以被电脑使用,不需要关心到底是鼠标还是键盘
案例:
#协议标准
class USB: #制定的USB协议,也就是其他类必须写成这种格式才符合规则 def open(self): pass def close(self): pass def read(self): pass def write(self): pass class Mouse(USB): #来了一个鼠标,继承USB父类 def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") def read(self): print("获取了光标位置....") def write(self): print("鼠标不支持写入....") class KeyBoard(USB): #又来了一个键盘,继承USB def open(self): print("键盘开机.....") def close(self): print("键盘关机了...") def read(self): print("获取了按键字符....") def write(self): print("可以写入灯光颜色....")
#接口 def pc(usb_device): #这是一个接口,只要把鼠标或者键盘等符合USB规则的类插进来立马运行 usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() pc(m) # 将鼠标传给电脑 k = KeyBoard() pc(k) # 来了一个键盘对象
在上述案例中,PC接口的代码一旦完成,后期无论什么样的设备 只要遵循了USB接口协议,都能够被电脑所调用
问题来了:
如果子类没有按照你的协议来设计,也没办法限制他,将导致代码无法运行,那怎么办呢?
class Mouse(): #不遵循USB协议还想使用pc接口,不报错才怪呢 def star(self): print("鼠标开机.....") def end(self): print("鼠标关机了...") def read_obj(self): print("获取了光标位置....") def write_obj(self): print("鼠标不支持写入....")
鼠标不遵循USB协议,自己搞了个协议,你往pc接口上插,一下就报错了,怎么办?
为了防止子类乱定义标准,USB协议中使用抽象类把子类的模式限制死了,必须一毛一样的模式
2. 抽象类 abc模块(abstract class)
(了解即可 没什么用)
抽象类指的是包含抽象方法(没有函数体的方法)的类,
作用:可以限制子类必须类中定义的抽象方法(上面的USB父类可以用此方法装饰,如果U盘不按套路来实例化的时候就会报错失败,不给插PC接口的机会)
import abc #抽象类要引入abc模块 class AClass(metaclass=abc.ABCMeta): #固定书写模式 @abc.abstractmethod #装饰哪个,子类中就必须有什么,少一个都不行,并且子类中每个方法下面必须有函数体,不能只写pass def run(self): pass @abc.abstractmethod def run1(self): pass class B(AClass): def run(self): print("runrunrurn...") #方法体内必须有执行代码,如果只写pass,实例化时也会报错 def run1(self): print("runrunrurn...") b = B() #如果只定义run 一个方法,不定义run1,会报错,父类中有几个,子类中就必须有几个方法,并且得一样
3. 鸭子类型
上面引用abc模块的抽象化确实可以限制子类的方法形式,但是python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该自觉遵守相关协议
所以有了鸭子类型这么一说:
如果这个对象长得像鸭子,走路像鸭子,那就他是鸭子
你只要保证你的类按照相关的协议类编写,也可以达到提高扩展性的目的
class Mouse: #我们不再定义USB协议,这里也不用再继承USB,只要自己乖乖的按照规则来写,pc接口就能识别 def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") def read(self): print("获取了光标位置....") def write(self): print("鼠标不支持写入....") class UDisk: def open(self): #这一次我不胡来了,我按规矩来,pc接口就能识别了 print("U盘启动了...") def close(self): print("U盘关闭了...") def read(self): print("读出数据") def write(self): print("写入数据") def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() pc(m) u = UDisk() pc(u)