封装
将复杂的丑陋的,隐私的细节 隐藏到内部,对外提供简单的使用接口
对外部隐藏了内部实现的细节,并提供访问接口
封装的目的
1.为了保证关键数据的安全性
2.对外部隐藏内部实现细节,隔离复杂度
什么时候需要封装
当数据不希望外界可以直接修改时
当有一些数据不希望给外界使用时
封装语法
初始化阶段,在被封装属性前加__ 如:self.__属性
class Person: def __init__(self, name, age, id_num): self.name = name self.age = age self.__id_num = id_num # 被封装隐藏 def check_id(self): # 给外界提供访问封装属性的接口 self.__id_num = 222 # 内部修改 print(self.__id_num) p = Person('xx', 20, 110110110) print(p.__id_num) # 报错 被隐藏,外界无法查看 p.id_num = 333 # 这是在对象名称空间中创建了一个变量 print(p.id_num) # 打印的是对象名称空间中的变量 p.__id_num = 555 # 这是在对象名称空间中创建了一个变量 print(p.__id_num) # 打印的是对象名称空间中的变量 p.check_id() # 外部通过接口访问
被封装的内容的特点:
1.外界(类外部)不能直接访问 (对象.属性名)
2.内部(类内部)依然可以直接使用
权限
学习了封装后就可以控制属性的权限
在python中只有两种权限
1.公开的,默认的都是公开的
2.私有的,只能当前类自己使用
外界访问私有内容
属性被封装后,外界需要使用时通过调用接口访问
通过定义方法类完成对私有(封装)属性的修改和访问
''' 一个下载器的类,需要提供一个缓存大小的属性 缓存大小不能超过内存限制 ''' class Download: def __init__(self, filename, url, buffer_memory): self.filename = filename self.url = url # 封装 缓存 self.__buffer_memory = buffer_memory # 外界调用下载功能 def start_download(self): # 判断缓存是否小于系统内存提供的缓存大小 if self.__buffer_memory <= 1024*1024: print('开始下载...') print('当前缓存大小:%s'%self.__buffer_memory) else: print('超过内存缓存,内存爆炸...') # 提供给外界修改封装缓存接口 def set_buffer_memory(self, size): if type(size) != int: print('请输入数字') else: self.__buffer_memory = size print('缓冲区修改成功') # 提供给外界访问缓存接口 def check_buffer_memory(self): return self.__buffer_memory d = Download('黑猫警长','www.heihei.com',1024*1030) # 修改 缓存 d.set_buffer_memory(1024*256) # 查看 缓存 d.check_buffer_memory() d.start_download() # 这样我们可以在外界修改这个关键数据时,做一些限制
property装饰器
通过提供的方法(接口)修改或访问属性,本身没有问题,但是给对象的使用者带来了麻烦,使用时必须知道哪些是普通属性,哪些是私有属性,需要使用不同的使用方式来调用他们
property装饰器就是为了使得调用方式一致,使访问方式和普通访问一样
三个有关的装饰器
1.@property 该装饰器用在属性的方法上 def 属性名(self): return self.__属性名 2.@属性名.setter 该装饰器用在修改属性的方法上 def 属性名(self,新属性值): '''一些条件''' self.__属性名 = 新属性值 3.@属性名.deleter 该装饰器用在删除属性的方法上 def 属性名(self): del self.__属性名 ps: setter 和 deleter 在有了 property 之后才能使用 注意(看下面题): key是被 property 装饰的方法(函数)的名称,也就是属性的名称 property 内部会创建一个对象key 所以在使用 setter 和 deleter 时,必须保证使用对象的名称调用方法 即: key.setter / key.deleter
例:
class A: def __init__(self, name, key): self.name = name self.__key = key @property def key(self): return self.__key @key.setter def key(self,new_key): # 可以加入一些判断 if type(new_key) == int: self.__key = new_key else: print('需要整型') @key.deleter def key(self): # print('该属性不允许删除') del self.__key print('key已删除') a = A('xx',110) print(a.key) # >>> 110 a.key = 220 print(a.key) # >>> 220 del a.key # >>> key已删除
python实现封装的原理
就是在加载类的时候,把__替换成了_类名__
class A: def __init__(self, name, pwd): self.name = name self.__pwd = pwd a = A('xx', 110) print(a.__dict__) # >>> {'name': 'xx', '_A__pwd': 110} print(a._A__pwd) # >>> 110
property 可以用来实现计算属性
计算属性指的是:属性的值,不能直接获得,必须通过计算才能获得
如:正方形面积
class Square: def __init__(self, length): self.length = length # self.area = length * length @property def area(self): return self.length * self.length s = Square(10) print(s.area) s.length = 20 print(s.area)
接口
接口是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码
接口本质就是一套协议标准,遵循这个标准的对象就能被调用
接口的目的就是为了提高扩展性
例如:电脑提前指定制定一套USB接口协议,只要你遵循该协议,你的设备就可以被电脑使用,不需要关系到底是鼠标还是键盘
class USB: def open(self): pass def close(self): pass def read(self): pass def write(self): pass class Mouse: def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") 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) class KeyBoard(USB): def open(self): print("键盘开机.....") def close(self): print("键盘关机了...") def read(self): print("获取了按键字符....") def write(self): print("可以写入灯光颜色....") # 来了一个键盘对象 k = KeyBoard() pc(k)
在上述案例中,PC的代码一旦完成,后期无论什么样的设备,只要遵循USB接口协议,都能够被PC锁调用
接口主要是方便了对象的使用者,降低使用者的学习难度,只要学习一套使用方法,就可以以不变应万变
如果子类没有按照你的协议来设计,也没有办法限制他,将导致代码无法运行
抽象类
指的是包含抽象方法(没有函数体的方法)的类
作用:可以限制子类必须类中定义方法
最后:python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该自觉遵守相关协议
如果这个对象长得像鸭子,走路像鸭子,那就他是鸭子
你只要保证你的类按照相关的协议类编写,也可以达到提高扩展性的目的