封装的概念
装指的是把属性装进一个容器,封指的是隐藏的意思,但是这种隐藏是对外隐藏,并不对内部隐藏。
如何封装
只需要在属性前加上 __ 开头,该属性就会被隐藏起来,该隐藏具备的特点有:
1、只是一种语法意义上的变形,即 __ 开头的属性会在检测语法时发生 “ _类名__属性名 ” 的变形
2、这种隐藏式是对外不对内的,因为在类的内部检测语法时所有的代码统一都发生了变形
3、这种变形只在检测语法时发生一次,在类定义之后新增的 __ 开头的属性并不会发生变形
4、如果父类不想让子类覆盖自己的属性,可以在属性前加 __ 开头
class Foo():
__x = 111 # _Foo__x
def __init__(self, m, n):
self.__m = m # self._Foo__m=m
self.n = n
def __func(self): # _Foo__func
print('Foo.func')
def func1(self):
print(self.__m) # self._Foo__m
print(self.__x) # self._Foo__x
print(Foo.__dict__)
# Foo.__x # 报错 AttributeError: type object 'Foo' has no attribute '__x'
print(Foo._Foo__x)
class Foo:
def __f1(self): # _Foo__f1
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.__f1() # self._Foo__f1
class Bar(Foo):
def __f1(self): # _Bar__f1
print('Bar.f1')
obj = Bar()
obj.f2()
# 运行
Foo.f2
Foo.f1
封装数据属性的真实意图
对于数据属性(属性),它的目的是将数据属性封装起来,在类外部的使用就无法直接操作该数据属性,而是需要从类的内部开一个接口给使用者,类的设计者可以在接口之上附加任意逻辑,从而严格控制使用者对属性的操作。
class People():
def __init__(self, name):
self.__name = name
def tell_name(self):
print('name: %s' %self.__name)
obj = People('湫兮')
obj.tell_name()
# 运行
name: 湫兮
上面的查看名字,可以控制输出格式,既然是控制,那就不仅仅是只有查看,还有增加、删除、修改的操作,但要注意,针对某一属性没有增加的说法,比如说对于这个属性的名字,肯定没有为这个属性增加名字的说法,因此只能删除和修改
class People():
def __init__(self, name):
self.__name = name
def tell_name(self):
print('name: %s' %self.__name)
def set_name(self, new_name):
self.__name = new_name
obj = People('湫兮')
obj.tell_name()
obj.set_name('qiuxi')
obj.tell_name()
上面是将名字 “湫兮” 改为 “qiuxi”,但这样写并不严谨,因为我不管改成什么类型都可以修改,而名字是一个字符串,因此要加上判断,如果修改的名字不是字符串类型那就输出提示信息,并且无法修改
class People():
def __init__(self, name):
self.__name = name
def tell_name(self):
print('name: %s' %self.__name)
def set_name(self, new_name):
if type(new_name) is not str:
print("名字必须是字符串类型")
return
self.__name = new_name
obj = People('湫兮')
obj.tell_name()
obj.set_name(123)
obj.tell_name()
# 运行
name: 湫兮
名字必须是字符串类型
name: 湫兮
删除也是一样的操作
class People():
def __init__(self, name):
self.__name = name
def tell_name(self):
print('name: %s' %self.__name)
def set_name(self, new_name):
if type(new_name) is not str:
print("名字必须是字符串类型")
return
self.__name = new_name
def del_name(self):
del self.__name
obj = People('湫兮')
obj.tell_name()
obj.set_name("qiuxi")
obj.tell_name()
obj.del_name()
print(obj.__dict__)
# 运行
name: 湫兮
name: qiuxi
{}
封装函数属性的真实意图
对于函数属性(方法),封装的目的是为了隔离复杂度。好比电脑开机,外部操作你只需要按下开机键,然后等待一会就可以使用了,但是在内部,从你按下开机键,它会做很多操作,比如电源供电、执行 BIOS 程序等,这些在内部是自动操作的。这就是一种封装的思想,按下开机键的背后封装了一堆的功能
# 取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
# 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性
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()
a = ATM()
a.withdraw()
property 装饰器
成人的 BMI 数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体脂参数(BMI)= 体重(kg)÷ 身高^2(m)
EX:70kg÷(1.75×1.75)= 22.86
class People():
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
# 体脂参数不是一个固定的值, 而是通过身高体重计算出来的
# 所以不能写在__init__里面作为对象的属性
def tell_bmi(self):
return self.weight / (self.height ** 2)
obj = People("qiuxi", 66, 1.74)
print(obj.tell_bmi())
这样写是能计算BMI了,但仔细想想,BMI 更像是一个人的数据属性,它是一个人的特征,此时在这里获取 BMI 却要通过调用 tell_bmi() 才能得到,这样写并不妥。可以设计一个解决方案,首先它的背后一定是计算 BMI 的功能,但是想要在访问的时候,把它伪装成数据属性能够直接访问。这个解决方案就是使用 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("qiuxi", 66, 1.74)
print(obj.bmi)
现在只是把函数属性伪装成数据属性去访问它,而对于数据属性,还有修改和删除操作。在数据属性中,修改就是直接赋值的操作,但在这里它不能直接赋值,因为它不是一个真正的数据属性,但我就是要修改,该怎么操作呢,这里我换一个例子
class People():
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
obj = People("qiuxi")
# 访问伪装成数据属性的name
print(obj.name)
但凡是被 property 装饰过的函数,这个函数下面都会生成一个新的装饰器 setter,setter 下面的函数名需要相同,因为对于使用者来说,操作都是使用 name 属性,只是查看和修改对应的功能不同
class People():
def __init__(self, name):
self.__name = name
# 查看
@property
def name(self):
return self.__name
# 修改
@name.setter
def name(self, new_name):
if type(new_name) is not str:
print("名字必须是字符串类型")
return
self.__name = new_name
obj = People("qiuxi")
print(obj.name)
obj.name = "湫兮"
print(obj.name)
修改也是相同的操作,被 property 装饰过的函数下面会生成一个新的装饰器 deleter
class People():
def __init__(self, name):
self.__name = name
# 查看
@property
def name(self):
return self.__name
# 修改
@name.setter
def name(self, new_name):
if type(new_name) is not str:
print("名字必须是字符串类型")
return
self.__name = new_name
# 删除
@name.deleter
def name(self):
del self.__name
obj = People("qiuxi")
print(obj.name)
obj.name = "湫兮"
print(obj.name)
del obj.name
print(obj.__dict__)
上面这三种操作是一种新式写法,还有一种旧式写法
class People():
def __init__(self, name):
self.__name = name
# 查看
def get_name(self):
return self.__name
# 删除
def del_name(self):
del self.__name
# 修改
def set_name(self, new_name):
if type(new_name) is not str:
print("名字必须是字符串类型")
return
self.__name = new_name
# 上面定义的顺序可以打乱, 但是这里property里面必须是查看、修改、删除的顺序
name = property(get_name, set_name, del_name)
obj = People("qiuxi")
print(obj.name)
obj.name = "湫兮"
print(obj.name)
del obj.name
print(obj.__dict__)
建议使用新式写法
以上就是封装的全部介绍,对于封装好的一个属性或方法,没事不要去访问,既然它封装了,目的就是为了不让你去访问它,然而你还去访问,这显然是不合理的