python语言中, 并没有专门的语句定义属性的可访问性,取而代之的是一些约定,认为程序员应当自觉根据这些约定来访问类的属性
私有属性约定
私有属性在其他语言中,表现为无法在类的外部直接修改,必须通过类内部的方法进行调用修改,从而保证类的封装性,python使用带_
下划线的属性名称标记为类的私有属性,这类属性不应当在类外部被修改或者直接访问, 使用_命名更像是protected, 供自身及子类访问
# _使用前缀单个下划线表示私有属性
# 私有属性不应当直接在外部修改该属性
class Zoom:
_scale = 5
def _resize(self):
return self._scale * self._origin
def _is_sun(self):
try:
if self._origin > 100:
return True
except:
print("放大镜不是照妖镜")
def zoom(self, origin):
self._origin = origin
if self._is_sun():
return "太阳照亮你的眼"
return self._resize()
# 私有属性可以被访问
# 完全靠编程人员自觉遵守, 两个zoom实例放大倍数为5
myzoom = Zoom()
myzoom2 = Zoom()
# 设计目的 - 程序员看到这个类属性,就应该知道只有zoom是可以直接访问的
myzoom.zoom(10)
myzoom.zoom(1000)
# 下列访问访问方式都不应该,直接破坏了封装性,得到了不正确的结果
# 通过实例修改变量 - 相当于定义了一个实例属性,该实例放大倍数发生变化
# 实例之间发生了不一致的行为,破坏了类的封装性
myzoom._scale = 10000
myzoom.zoom(10)
myzoom2.zoom(10)
# 通过类名修改变量, 会修改所有实例的_scale,放大倍数无法控制
Zoom._scale = 1000
myzoom.zoom(10)
myzoom2.zoom(10)
# 提供细节的内部方法不应该从外部访问
myzoom._resize()
myzoom._is_sun()
# 通过继承实现一个10倍放大镜
# 子类可以从父类继承到_开头的属性
class ZoomX10(Zoom):
_scale = 10
myzoom = ZoomX10()
myzoom.zoom(10)
myzoom.zoom(1000)
名称改写机制
通过名称改写机制可以防止父类属性被子类覆盖,凡以二条下划线开头的__属性名称
都将被改写为_classname__identifier,这种改名机制更类似于private
# 有的时候某些父类属性不想被覆盖
class Zoom:
_scale = 5
__doc = "不能放大100以上的物体"
def _resize(self):
return self._scale * self._origin
# 任何子类都不能修改放大限制
# __两根_开头
def __is_sun(self):
try:
if self._origin > 100:
print(self.__doc)
return True
except:
print("放大镜不是照妖镜")
def zoom(self, origin):
self._origin = origin
if self.__is_sun():
return "太阳照亮你的眼";
return self._resize()
# 正常使用
myzoom = Zoom()
myzoom.zoom(10)
# 对于名称为两个__开头的属性,不能直接通过原名访问
print(myzoom.__doc)
# 对于名称为两个__开头的属性,子类不能覆盖
class ZoomX10(Zoom):
_scale = 10
__doc = "只可以放大1000以下的物体"
def __is_sun(self):
try:
if self._origin > 1000:
print(self.__doc)
return True
except:
print("放大镜不是照妖镜")
# 正常使用
myzoom = ZoomX10()
myzoom.zoom(10)
# 尝试突破放大限制
myzoom.zoom(500)
# 之所以可以这样,是因为属性名发生了变化
# 如果属性前使用了二条下划线__那么属性名称会被改写为_classname__identifier
print(Zoom.__dict__)
print(ZoomX10.__dict__)
property语法糖
提升对内部受保护变量的访问便利性
# 有的时候我们希望用点语法直接修改保护属性
# 比如A类的内部一个属性名__x, 希望通过a.x = 100修改
# 使用property来封装类
class C:
def __init__(self, value):
self.__x = value
def getx(self):
return self.__x
def setx(self, new_value):
'''检查value合法性,并设置为_x的新值
'''
pass
self.__x = new_value
def delx(self):
del self.__x
# c.x 通过 getx获得x值
# c.x 通过 setx设置x值
# del c.x 通过delx删除x
x = property(getx, setx, delx, "I'm the 'x' property.")
c = C(100)
print(c.x)
c.x = 1000
print(c.x)
del c.x
print(c.x)
通过内置函数property将x作为C类的一个属性,可以对x进行如下操作:
c.x
通过getx
获得x
值c.x
通过setx
设置x
值del c.x
通过delx
删除x
通过这样的方式来提升类的封装性
将property作为装饰器使用
# 将property作为装饰器使用
class C:
def __init__(self, value):
self.__x = value
@property
def x(self):
try:
return self.__x
except:
print("__x属性已被删除")
@x.setter
def x(self, value):
try:
self.__x = int(value)
except:
print("value类型不正确,设置失败")
@x.deleter
def x(self):
try:
del self.__x
except Exception as e:
print(e)
else:
print("__x被删除")
c = C(10)
print(c.x)
c.x = 100
print(c.x)
c.x = "abc"
del c.x
print(c.x)
这两种封装x属性的方式完全等价
私有属性练习题
class Woman:
def __init__(self, name, age):
pass
# 添加读取name、age方法
# 添加修改name、age方法、禁止直接通过属性修改name、age
# 比如实例为cuihua
# cuihua.name 打印cuihua姓名
# cuihua.age 打印cuihua年龄
# 但是不允许 cuihua.name = "王三", cuihua.age=28
讨论区