一、封装的引入
1、封装< -- >整合
# 封装是面向对象三大特性最核心的一个特性 封装指的就是把数据与功能都整合到一起
除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口
2、将封装的属性进行隐藏
Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动
变成“_类名__属性名”的形式:
class Foo: __N=0 # 变形为_Foo__N def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形 self.__x=10 # 变形为self._Foo__x def __f1(self): # 变形为_Foo__f1 print('__f1 run') def f2(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形 self.__f1() #变形为self._Foo__f1() print(Foo.__N) # 报错AttributeError:类Foo没有属性__N obj = Foo() print(obbj.__x) # 报错AttributeError:对象obj没有属性__x
总结:
#1、在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如Foo._A__N,所以说这种操作并没有严格意义上地
限制外部访问,仅仅只是一种语法意义上的变形。 #2、这种隐藏对外不对内,因为__开头的属性会在检查类体代码语法时统一发生变形 #3、这种变形操作只在检查类体语法的时候发生一次,之后定义的__开头的属性都不会变形
二、开放接口
定义属性的目的是为了使用,所以隐藏不是根本目的
1、隐藏数据属性
将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制
>>> 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
2、隐藏函数属性
目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能
接口即可,其余功能我们都可以隐藏起来
>>> 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() ... >>> obj=ATM() >>> obj.withdraw()
三、property装饰器
Python中专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果。
我们以BMI为例子
#BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为: 体质指数(BMI)=体重(kg)÷身高^2(m) EX:70kg÷(1.75×1.75)=22.86
#!/usr/bin/env python class People: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def get_bmi(self): return self.weight / (self.height ** 2) people_obj1 = People('姜春', 60, 1.75) print(people_obj1.get_bmi)
使用property可以保证属性访问的一致性。此外,property还提供了设置,删除的属性功能,如下
class People: def __init__(self, name): self.__NAME = name @property def show_name(self): return self.__NAME #将属性隐藏起来 @show_name.setter def show_name(self,value): if not isinstance(value,str): #在设定值之前进行类型检查 raise TypeError('{} must be str'.format(value)) self.__NAME = value #通过类型检查后,将值value存放到真实的位置self.__NAME @show_name.deleter def show_name(self): raise PermissionError('can not delete') p = People('姜春') print(p.show_name) #姜春 p.show_name = '王鹏' print(p.show_name) #王鹏 p.show_name = 1024 print(p.show_name) #TypeError: 1024 must be str del p.show_name #PermissionError: can not delete