封装、隐藏和property装饰器
封装
封装指的是将类中的多个属性或方法封装成一个接口,这样当我们调用这一个接口的时候,不需要去考虑该接口背后进行了什么操作,只需要知道该接口提供了什么功能即可。封装的主要优点是隔离复杂度。
# 定义一个相机类,每次照相都需要进行调整光圈、延时和按快门
class Camera:
def set_aperture(self):
print('调光圈')
def set_delayed(self):
print('调延时')
def shutter(self):
print('按快门')
camera = Camera()
camera.set_aperture()
camera.set_delayed()
camera.shutter()
而傻瓜式相机只需要按快门即可拍照,不用考虑光圈、延时。
class Camera:
def set_aperture(self):
print('调光圈')
def set_delayed(self):
print('调延时')
def shutter(self):
print('按快门')
def take_photo(self):
self.set_aperture()
self.set_delayed()
self.shutter()
camera =Camera()
camera.take_photo() # 这样使用者无需考虑内部做了什么操作,仅调用接口即可。
但封装后的属性与方法还是能直接被使用者以对象.属性
的方式调用,如果我们仅想让使用者仅能使用我们提供的功能,那么我们可以将不想暴露给使用者的属性隐藏起来(设置成私有的)。
隐藏属性
Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),封装的属性可以直接在内部使用,而不能被外部直接使用,但其实这仅仅只是一种变形操作,类中所有双下划线开头的属性都会再类定义阶段、检测语法时自动变成_类名__属性名
的形式。
隐藏方法:作用1,隔离复杂度。
class Camera:
__money = 2000 # 变形为_Camera__money
def __set_aperture(self):
print('调光圈')
def __set_delayed(self): # 变形为 _Camera__set_delayed
print('调延时')
def __shutter(self):
print('按快门')
def take_photo(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形.
self.__set_aperture()
self.__set_delayed()
self.__shutter() # 变形为self._Camera__shutter
camera =Camera()
camera.take_photo()
camera.__shutter() # 保错 AttributeError: 'Camera' object has no attribute '__shutter'
作用2:在继承中,如果父类不想让子类覆盖自己的方法,可以将方法定义为私有的。
class A:
def __f1(self): # __f1()已经变形为_A__f1
print('A.__f1')
def test(self):
self.__f1() # 这里的__f1也变形为_A__f1,相当于调用" 对象._A__f1 ",会先从b对象自身找,再从B类找,没有则从A类中找。
class B(A):
def __f1(self): # __f1变形为_B__f1
print("B.f1")
def __f2(self):
print('B.f2')
b = B()
b.test()
A.__f1
隐藏属性:隐藏起来后对外提供操作该数据的接口,然后开发者就可以在接口附近加上对该数据操作的限制,以此来完成对数据属性操作的严格控制,保证数据安全。
class Coder:
def __init__(self,name):
if name.startswith('a'):
print('不能以a开头')
else:
self.__NAME = name
def info(self):
return self.__NAME
x = Coder('abc')
不能以a开头
这种变形需要注意的问题是:
1、在类外部无法直接访问双下划线开头的属性,但知道了类名和属性名就可以拼出名称:_类名__属性
,然后就可以访问了,所以隐藏并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。
print(Camera.__dict__)
{......'_Camera__money': 2000, '_Camera__set_aperture': <function Camera.__set_aperture at
0x0000000002778B80>, '_Camera__set_delayed': <function Camera.__set_delayed at 0x0000000002778C10>,
'_Camera__shutter': <function Camera.__shutter at 0x0000000002778CA0>, 'take_photo': <function
Camera.take_photo at 0x0000000002778D30>,......}
2、在类内部是可以直接访问双下划线开头的属性的,因为在类定义阶段类内部的双下划线开头的属性统一发生了变形。
camera._Camera__shutter() # 这种方式也能访问到.
按快门
3、变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。
Camera.__color = 'blue'
print(Camera.__dict__)
{.......'__color': 'blue'}
隐藏属性与开放接口本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名和参数不变,那么无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。
Python并不会真的阻止访问私有的属性,模块也遵循这种约定,如果模块内的成员以单下划线开头,那么from module import *
时不能被导入,但是不使用*的方式直接指定成员名也是可以导入的,这些以单下划线开头的都是私有的,原则上是供内部调用,作为外部使用者其实也能强行调用。
注意:封装是将多个属性或方法封装成一个方法或称接口,使用者调用这一个接口即可使用这些被封装的属性。隐藏属性是将开发者不希望暴露给使用者的属性给隐藏住。二者虽然经常搭配使用,但要区分清楚。
property
对于某些方法,它们所返回的结果更像是对象的属性,而非调用功能。比如拿到圆的半径,调用计算面积的方法得到面积结果,面积更像是圆的属性,而非功能。为此Python专门提供了一个装饰器函数property,可以将类中的函数 “ 伪装成 ” 对象的数据属性,对象在访问该特殊属性时,会触发功能的运行,然后将返回值作为本次访问的结果,例如
class Circular:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
from math import pi
return pi * self.radius ** 2
c1 = Circular(10)
print(c1.area)
314.1592653589793
使用property有效地保证了属性访问的一致性。另外面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种方式不对外公开,但对子类或者朋友(friend)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现:
class Person:
def __init__(self,name):
self.__NAME = name
@property
def name(self):
return self.__NAME # obj.name 访问的是self.name,这也是真实值.
@name.getter
def name(self):
return self.__NAME, '哈哈' # getter就是obj.name 时返回的值,会覆盖掉property返回的值.
@name.setter
def name(self, value):
if not (type(value) is str): # 在设定值之前进行类型检查.
print(f'name must be str')
self.__NAME = value # 通过检查后,才会将值value放到真实的位置self.name
@name.deleter
def name(self):
del self.__NAME
lisi = Person('lisi')
lisi.name = 11 # 在修改name属性时,会调用@name.setter装饰后的name方法
# name must be str
print(lisi.name) # 在查看name属性时,会调用@name.getter装饰后的方法,如果没有getter则会调用property装饰后的方法.
# (11, '哈哈')
del lisi.name # 会调用@name.deleter装饰后的name方法.
print(lisi.name)
# AttributeError: 'Person' object has no attribute '_Person__NAME'
必须有property装饰后,才能使用getter、setter和deleter,如果有getter那么就不会调用property。
隐藏属性隐藏的是类内的属性,对象也可以定义与属性同名的属性,与property的setter配合使用,就可以限制住对象定义的同名属性。