10.3.1 封装简介
封装是面向对象编程中的三大特性之一,即在设计类时,刻意地将一些实例属性和实例方法隐藏在类的内部,这样在使用此类时,将无法直接以"对象.实例属性(或实例方法)"等形式调用,而是只能用未隐藏的类方法间接操作这些隐藏的实例属性或实例方法。
类中的实例属性和实例方法如果没有被封装,就可以被外部随意调用,这是一种非常危险的操作。例如,在"手机"类中有一些属性是保密的技术,是不想让其他人随意就能获取到的,且在"手机"类中的电压和电流等属性,需要规定在一定范围内,更是不能被随意赋值更改的,如果对这些属性随意的赋值更改,比如给其电压赋上380V的值,就会严重破坏"手机"类。
所以对类进行封装有以下3点好处:第一,封装保证了类内部数据结构的完整性,因为使用类的用户无法直接使用类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性;第二,对类的良好封装,使得用户只能借助暴露出来的类方法来访问数据,所以只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松控制用户对类中属性或方法的不合理操作;第三,类的封装可以使类的外部不能随意调用类内部的数据,从而有效地避免了外部错误对类本身的"交叉感染",使程序错误能够局部化,大大减小查错的难度。
10.3.2 私有属性和私有方法
与其它面向对象的编程语言不同,Python中的实例属性和实例方法,不是公有的,就是私有的,其区别如下:公有的实例属性和实例方法,在类的外部、类的内部以及其子类中都可以正常访问;私有的实例属性和实例方法,只可以在类的内部正常访问,类的外部以及其子类都无法访问。
但是,Python并没有像其它面向对象的编程语言一样提供public、private修饰符。所以,为了实现类的封装,Python通过在实例属性和实例方法的名称前添加双下划线,以表示该实例属性或实例方法为私有的。
除此之外,还可以定义以单下划线开头的实例属性或实例方法,这样的实例属性或实例方法虽然在外部也是可以正常访问的,但是,按照约定俗成的规定,这种实例属性或实例方法通常被视为私有属性或私有方法,意思是"虽然该实例属性或实例方法可以在类的外部被正常访问,但是,请把其视为私有属性或私有方法,请不要在类的外部随意访问"。
需要注意的是,在Python的类中还有以双下划线开头和结尾的属性或方法,这些都是Python内置的,用于Python的内部调用,所以在自定义私有属性或私有方法时,不要使用这种格式。
1.私有属性
在实例属性名称前添加双下划线创建私有属性。示例代码如下:
# 资源包\Code\chapter10\10.3\1014.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def showInfo(self):
return self.__electricity[3]
huawei = Phone()
# 通过未封装的方法访问私有属性
print(huawei.showInfo())
# 报错,私有变量不能在类外部正常访问
print(huawei.__electricity[3])
那么,双下划线创建的私有属性是不是一定不能从类的外部访问呢?其实并不是,不能直接访问私有属性的原因是Python解释器对外将私有属性改为"_类名+私有属性"的形式,所以,在类的外部仍然可以通过使用"对象._类名+私有属性"的形式来访问私有属性。但是强烈建议读者不要使用此种方式访问私有属性,因为不同版本的Python解释器会把私有属性改为不同的形式,并且会破坏程序的封装性。示例代码如下:
# 资源包\Code\chapter10\10.3\1015.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def showInfo(self):
return self.__electricity[3]
huawei = Phone()
print(huawei._Phone__electricity[3])
在实例属性名称前添加单下划线创建私有属性。示例代码如下:
# 资源包\Code\chapter10\10.3\1016.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self._electricity = [10, 50, 100, 200, 300]
def showInfo(self):
return self._electricity[3]
huawei = Phone()
# 通过未封装的方法访问私有属性
print(huawei.showInfo())
# 通过单下划线定义的私有属性可以在类的外部正常访问,但是按照约定,不要在类的外部访问
print(huawei._electricity[3])
2.私有方法
在实例方法名称前添加双下划线创建私有方法。示例代码如下:
# 资源包\Code\chapter10\10.3\1017.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def __startupCurrent(self):
return (f'开机电流为:{self.__electricity[3]}mA')
def showInfo(self):
return self.__startupCurrent()
huawei = Phone()
# 通过未封装的方法访问私有方法
print(huawei.showInfo())
# 报错,私有方法不能在类外部正常访问
print(huawei.__startupCurrent())
双下划线创建的私有方法与双下划线创建的私有属性类似,在类的外部也可以通过使用"对象._类名+私有方法"的形式来访问私有方法。但是,同样强烈建议读者不要使用此种方式,原因与双下划线创建的私有属性一致。示例代码如下:
# 资源包\Code\chapter10\10.3\1018.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def __startupCurrent(self):
return (f'开机电流为:{self.__electricity[3]}mA')
def showInfo(self):
return self.__startupCurrent()
huawei = Phone()
print(huawei._Phone__startupCurrent())
在实例方法的名称前添加单下划线创建私有方法。示例代码如下:
# 资源包\Code\chapter10\10.3\1019.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def _startupCurrent(self):
return (f'开机电流为:{self.__electricity[3]}mA')
def showInfo(self):
return self._startupCurrent()
huawei = Phone()
# 通过未封装的方法访问私有方法
print(huawei.showInfo())
# 通过单下划线定义的私有方法可以在类的外部正常访问,但是按照约定,不要在类的外部访问
print(huawei._startupCurrent())
上面的程序中,私有属性都被"隐藏"了起来,如果想对类中的私有属性赋值,或者是访问类中的私有属性,除了可以使用未被封装的实例方法,还以通过使用函数装饰器@property或函数装饰器@私有属性.setter。
(1)使用未被封装的实例方法。
示例代码如下:
# 资源包\Code\chapter10\10.3\1020.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
def inputInfo(self, electricity):
self.__electricity = electricity
def showInfo(self):
return self.__electricity
huawei = Phone()
# 为私有属性赋值
huawei.inputInfo([10, 50, 100, 200, 300, 500])
# 访问私有属性
print(huawei.showInfo())
(2)使用函数装饰器@property和函数装饰器@私有属性.setter。
# 资源包\Code\chapter10\10.3\1021.py
class Phone(object):
def __init__(self):
self.__voltage = 3.7
self.__electricity = [10, 50, 100, 200, 300]
# 注意,装饰器@property要在装饰器@私有属性.setter之前
@property
def electricity(self):
return self.__electricity
@electricity.setter
def electricity(self, electricity):
self.__electricity = electricity
huawei = Phone()
# 为私有属性赋值
huawei.electricity = [10, 50, 100, 200, 300, 500]
# 访问私有属性
print(huawei.electricity)
通过上面的2段代码,可以看出使用函数装饰器的方式为私有属性赋值或访问私有属性相对于使用未被封装的实例方法更加简洁明了。