请稍候,加载中....

类的私有属性

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

 


Python学习手册-