面向对象编程
一、封装的概念
- 面向对象编程有三大特性:封装,继承,多态。其中最重要的特性就是封装
- 封装:就是把数据与功能都整合到一起,上一节我们一直提到的整合,就是封装的通俗讲法。
- 针对封装到对象或者类中的属性,我们还可以严格控制它们的访问,分两步实现(隐藏与开放接口)
二、隐藏属性
- 在属性前面加上 __ 就可以实现对外隐藏属性。
class Foo:
__x = 1
def __f1(self):
print(self)
print(Foo.__x) # 报错 AttributeError: type object 'Foo' has no attribute '__x' 找不到对应属性
# 我们来查看到底发生了什么事情
print(Foo.__dict__)
# {'__module__': '__main__', '_Foo__x': 1, '_Foo__f1': <function Foo.__f1 at 0x000001EDC95AC5E0>...}
# 查看之后发现,属性是存在的,只是属性名发生了变化(在属性的前面加了“_类名”)
# 所以:
print(Foo._Foo__x) # 1 遵循隐藏后的变化,这样是可以访问到的
总结:给属性加 __ 这种操作并没有严格意义上限制外部访问,只是一种语法意义上的变形
- 这种隐藏是对内不对外的(因为__开头的属性在,检查代码语法的时候会统一发生变形)
- 在 obj.f2() 运行 的时候,类中的代码已经运行完毕了。而检查语法是运行之前就完成的,所以改变形都变形过了
- 这种变形只在在检查语法阶段发生一次,之后再定义 __ 开头的属性,都不会发生变形。(比如类体外的Foo.__x = 5)
class Foo:
__x = 1 # 当 __x 变成 _Foo__x 时
def __init__(self):
self.__x = 10
def __f1(self): # 当 __f1 变成 _Foo__f1 时
print("正在运行f1")
def f2(self): # 类体内非隐藏属性
self.__f1() # 类体内调用隐藏属性 其实:这里的_f1 变成也了 _Foo__f1
obj = Foo()
obj.f2() # 正在运行f1
三、开放接口
定义属性就是为了使用,隐藏不是目的。(所以隐藏有更深的用意)
实际应用1:
- 将数据隐藏起来限制外部对数据的直接操作,然后提供给外部可以访问属性的接口,接口可以附加额外的逻辑对外部操作进行严格的控制
# 设计者
class People:
def __init__(self,name,age):
self.__name = name # 使用者无法访问隐藏属性
self.__age = age
def get_info(self):
# 通过该查看接口可以访问被隐藏的属性
print(f"名字:{self.__name},年龄:{self.__age}")
def set_info(self,name,age):
if not isinstance(name,str): # 判断得到的数据是否是str类型,如果不是,给出错误提示
raise TypeError("名字必须是字符串类型")
if not isinstance(age,int): # 判断得到的数据是否是int类型,如果不是,给出错误提示
raise TypeError("年龄必须是整型")
self.__name = name # 使用者传入数据都符合条件之后,允许对属性进行修改
self.__age = age
# 使用者
obj = People("nida",18)
obj.get_info() # 名字:nida,年龄:18
obj.set_info(18,18) # TypeError: 名字必须是字符串类型
obj.set_info("nida","18") # TypeError: 名字必须是字符串类型
obj.set_info("nida2",19) # 传入符合条件的参数
obj.get_info() # 名字:nida2,年龄:19 改动成功
备注:这个特性非常重要,设计者可以对这个接口设置各种条件,来进行过滤。(可以让你访问,但是你要先符合我的条件)
实际应用2:
- 把内部的逻辑函数全部隐藏,只对外提供一个开发接口,这样使用者使用起来就非常简洁,目的是为了隔离复杂度。
class ATM:
def __card(self):
print("插卡")
def __auth(self):
print("用户认证")
def __input(self):
print("输入取款金额")
def __money(self):
print("取款")
def withdraw(self):
print("取款功能")
self.__card()
self.__auth()
self.__input()
self.__money()
obj = ATM()
obj.withdraw() # 取款功能/插卡/用户认证/输入取款金额/取款
总结:隐藏属性与开放接口,本质就是为了明确的区分内外。内部可以修改封装内的东西而不影响外部调用者的的代码。而类外部只需要一个接口,只要接口名,参数不变,无论设计者如何改变内部实现代码,使用者都不用改变自己的代码。
四、property
知识回顾:装饰器,是在不修改被装饰对象源代码及调用方式的前提下为被装饰对象,添加功能
property就是一个装饰器
实际应用:
BMI是 用来衡量一个人的体重与身高对健康影响的一个指标(计算公式:体质指数(BMI)=体重(kg)÷身高^2(m))
- 身高和体重是不断变化的,所以每次想查看BMI,都需要通过计算才能得到,但是很明显BMI听起来更像是一个特征而非功能,为此python专门提供了一个装饰器property,可以将类中的函数“伪装”成对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果:
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)
obj = People("nida",50,1.6)
print(obj.bmi) # 访问函数就像访问属性一样的感觉
- 使用property有效的保证了属性访问的一致性,另外property还提供设置和删除的属性功能:
class Foo:
def __init__(self,name):
self.__name = name
@property
def name(self): # 查看名字属性
return self.__name
@name.setter
def name(self,new_name): # 修改名字属性
if not isinstance(new_name,str):
raise TypeError("名字必须是字符串")
self.__name = new_name
@name.deleter
def name(self): # 删除名字属性
raise TypeError("不允许删除")
obj = Foo("nida")
print(obj.name) # nida
obj.name = "yaoyao"
print(obj.name) # yaoyao
del obj.name # TypeError: 不允许删除
备注:通过property(把全部功能函数全部取名与第init内参数名一致,然后在函数上加上对应的property语法),即可将查看/修改/删除等功能函数,伪装的跟没有被隐藏过一样。