面向对象之封装
一、封装--隐藏属性
1、从封装的本意上理解,封装就是将一些东西放到一个盒子或袋子里包起来,然后密封起来。
如果按照这种理解的话,封装就等于“隐藏”,但是这是不准确的,很片面。
2、那么我们先来看下怎样隐藏属性吧!
在Python中用 双下划线开头 的方式将属性隐藏起来(也可看成是设置成私有的)
3、示例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
class A:
__x = '未知' # '_A__x': '未知'(__x是隐藏属性的一种格式,定义时便被改写成 '_A__x')
def __init__(self,name):
self.__name = name # 改写成了self._A__name = 'cc'
def __func(self): # 改写成了 _A__func
print('from __func')
def foo(self):
self.__func() # 在类内部里可以直接使用的原因:被改写成了self._A__func()
print('from foo')
print(A.__dict__)
a = A('cc')
print(a.__dict__) # {'_A__name': 'cc'}
# print(a.__name) # 无法找到__name 在类外部无法直接使用:obj.__AttrName
a.foo()
'''输出:
from __func
from foo
'''
4、加双下划线隐藏属性的特点:
<1> 在类外部无法直接使用:obj.__AttrName -->无法获取
<2> 在类内部可以直接使用:self.__AttrName -->直接使用
<3> 子类无法覆盖父类双下划线开头的属性。如下所示:
class Func:
def __func2(self): # 在内部被改写成 _Func__func2
print('from func2')
class Bar(Func):
def __func2(self): # 在内部被改写成 _Bar__func2
print('from Bar')
# foo = Bar()
# foo.func2() # from Bar
5、需要注意的几点:
<1> 这种机制并没有真正意义上限制我们从外部直接访问属性,当我们知道了类名和属性名就可以
拼出名字了:_类名__属性,然后就可以访问了。
<2> 变形的过程只在类的定义时发生一次,在定义后的赋值操作(添加或更改属性),不会变形。
<3> 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有。
# 验证注意的问题1:
print(a._A__name) # cc
# 验证注意的问题2:
A.__y = 666
print(A.__dict__) # '_A__x': '未知', '__y': 666 可见并未实现隐藏
a.__age = 18
print(a.__dict__) # {'_A__name': 'cc', '__age': 18} # 也未实现隐藏
print(a.__age) # 18 # 可直接访问
# print(a.__name) # 报错
# # 验证注意的问题3:
class Func:
def __func2(self): # _Func__func2
print('from Func.__func2')
def bar(self):
print('from Func.bar')
self.__func2() # 定义时被改写成 self._Func__func2,所以此时只在自身里调用,避免被子类覆盖
class Foo(Func):
def __func2(self): # _Foo__func2
print('from Foo.__func2')
f = Foo()
f.bar() # from Bar
'''
输出:
from Func.bar
from Func.__func2
'''
二、封装的意义
1、封装数据属性:明确区分内外,控制外部对隐藏属性的操作行为。
示例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
class People:
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):
print('名字必须为字符串')
return
if not isinstance(age,int):
print('年龄必须为整数')
return
self.__name = name
self.__age = age
p1 = People('cc',21)
print(p1.__dict__) # {'_People__name': 'cc', '_People__age': 21}
p1.tell_info() # 姓名:【cc】 年龄:【21】
p1.set_info(688,21) # 名字必须为字符串
p1.set_info('sc','21') # 年龄必须为整数
2、封装方法:隔离复杂度
示例1:
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('输密码')
def __input(self):
print('输入金额')
def __take_money(self):
print('取出钱')
def __print_bill(self):
print('打印账单')
def take_money(self): # 用户不用关心具体的流程,只需要调用取钱接口即可。
self.__card()
self.__auth()
self.__input()
self.__take_money()
self.__print_bill()
p1 = ATM()
p1.take_money()
'''用户仅需一步就可以实现所有操作
插卡
输密码
输入金额
取出钱
打印账单
'''
示例2:如傻瓜相机
class Autofocus_camera:
def __get_sence(self):
print('取景')
def __aiming(self):
print('调光')
def __focus(self):
print('聚焦')
def __shutter(self):
print('快门')
def take_photo(self):
self.__get_sence()
self.__aiming()
self.__focus()
self.__shutter()
person = Autofocus_camera()
person.take_photo()
'''# 隔离复杂度,简化用户的操作
取景
调光
聚焦
快门
'''
注意:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
三、封装与扩展性
封装明确区分内外,使得类的设计者可以修改封装内的东西而不影响外部调用者的代码;
而外部使用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码则无需改变。
这极大的提升了程序的扩展性。
示例:
# 类的设计者
class House:
def __init__(self,owner,position,length,width):
self.owner = owner
self.position = position
self.price_m = price_m
self.__length = length
self.__width = width
def tell_sum(self): # 对外提供的接口,隐藏了内部的实现细节,此时是计算面积
return self.__length*self.__width
# 使用者
p1 = House('cc','hgys',18,20)
print(p1.tell_sum()) # 360 #使用者调用接口 tell_sum,获取面积结果
# 类的设计者,现在打算扩展功能,而要求使用者不需要改变操作方法
class House:
def __init__(self,ower,position,length,width,price_m):
self.owner = owner
self.position = position
self.length = length
self.width = width
self.price_m = price_m
def tell_sum(self): # 对外提供接口,隐藏内容,此时求房价
return self.length*self.width*self.price_m
# 扩展后使用者的使用方法
p2 = House('cc','hgnf',10,15,10000)
print(p2.tell_sum()) # 1500000 # 调用方法不变,同样调用tell_sum接口,降低使用者的使用难度
用户无需关心内部具体程序,只需调用teLl_sum()方法即可获取信息
当内部修改时,用户调用方式仍然不变,极大地提高了程序的可扩展性
四、property(特性)装饰器的使用
1、定义: @property 属性装饰器,如果需要调用某一经过计算的属性(使用函数)时,但我们想以数据属性的方式调用,就要用到@property。
2、示例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
'''
BMI指数(即身体质量指数,简称体质指数又称体重,英文为Body Mass Index,简称BMI),
是用体重公斤数除以身高米数平方得出的数字,是目前国际上常用的衡量人体胖瘦程度以及是否健康的一个标准。
它的定义如下:
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
成人的BMI数值(参考):
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
'''
class People:
def __init__(self,name,age,height,weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
@property # 本意属性,特性
def bmi(self):
return self.weight/(self.height**2)
p1 = People('cc',21,1.70,63)
# print(p1.bmi()) # 21.79930795847751 # 调用方法不合适,容易引起误解,bmi更像是一个数据属性,而非函数
print(p1.bmi) # 21.79930795847751 # 这样更符合使用习惯,虽然本质上仍然调用的是方法,但这个方法已经经过装饰,和调用数据属性方法别无二致
p1.weight = 60
print(p1.bmi) # 20.761245674740486
3、property特性装饰器的扩展
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Per:
def __init__(self,name):
self.__name = name
@property # 查询
def name(self):
print('from property.name')
return self.__name
@name.setter # 设置
def name(self,s):
if not isinstance(s,str):
print('名字必须为字符串')
return
self.__name = s
@name.deleter # 删除
def name(self):
print('你无权删除')
p = Per('ht')
# print(p.name()) # ht
print(p.name) # ht 查询属性
p.name = 'cc' # 增加了@name.setter才能更改属性
p.age = 18
print(p.name,p.age) # cc 18
del p.age # 同样增加了@name.deleter才能删除属性
print(p.__dict__) # {'_Per__name': 'cc'}