访问控制:私有属性和私有方法、保护变量
面向对象三要素之一:封装
属性装饰器,@property(getter),setter,deleter
property(fget=None, fset=None, fdel=None, doc=None)

私有属性


有些属性不希望外部可以访问或者修改,python 提供私有属性解决这个问题。

  • 私有属性:使用双下划线开头的属性名
  • 私有属性的本质:如果声明一个实例变量使用双下划线开头,python 解释器会将其改名,转换名称为_类名__变量名
class Person:
    def __init__(self,name,age):
        self.name = name
        self.__age = age
    
    def getage(self):
        return self.__age

j = Person('jerry',18)
# print(j.__age) # AttributeError: 'Person' object has no attribute '__age'
j.__age = 19 # 动态增加__age 属性
print(j.getage())
print(j.__dict__) # 可以看出__age私有属性被修改成了_Person__age

j._Person__age = 19 # 虽然知道修改了名称,但一般不建议这么做
print(j.getage())
print(j.__dict__)
-------------------------------------------------
18
{'name': 'jerry', '_Person__age': 18, '__age': 19}
19
{'name': 'jerry', '_Person__age': 19, '__age': 19}

保护属性


class Person:
    def __init__(self,name,age):
        self.name = name
        self._age = age

j = Person('jerry',18)
print(j.__dict__)
-----------------------
{'name': 'jerry', '_age': 18}
  • 保护属性不做改名,和普通属性一样,解释器不做任何处理
  • 只是开发者共同的约定,这种变量等同私有变量,最好不要直接使用

私有方法


  • 同私有属性,使用双下划线命名的方法
  • 通过改名隐藏,改成_类名__方法名
class Person:
    def __init__(self,name,age):
        self.name = name
        self.__age = age
    
    def __getage(self):
        return self.__age

j = Person('jerry',18)
# print(j.__getage) # AttributeError: 'Person' object has no attribute '__getage'
print(j.__dict__)
print(j.__class__.__dict__.keys())
print(Person.__dict__.keys())
print(j._Person__getage())
---------------------------------
{'name': 'jerry', '_Person__age': 18}
dict_keys(['__module__', '__init__', '_Person__getage', '__dict__', '__weakref__', '__doc__'])
dict_keys(['__module__', '__init__', '_Person__getage', '__dict__', '__weakref__', '__doc__'])
18

综上,在 Python 中使用 单下划线或者双下划线来标识一个成员被保护或者被私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员
Python 中没有绝对的安全的保护成员或者私有成员。因此,下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们

补丁

通过替换修改来增强扩展原有代码的能力。可用于调试 mock 数据等。
不做深入解释,可参考其他资料。

封装


  • 面向对象三要素之一:封装(encapsulation)
  • 将数据和操作组织到类中,即属性和方法
  • 通过访问控制,将数据隐藏起来,给使用者提供操作(方法),使用者通过操作就可以获取或者修改数据如 getter 和 setter

属性装饰器


属性保护起来了,不直接从外部访问,那需要提供外部访问的接口,如 getter 读取属性和 setter 设置属性

@property

# 可以这样,提供getage和setage方法
class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    def getage(self):
        return self.__age
    
    def setage(self, age):
        self.__age = age

j = Person('jerry')
j.setage(20)
j.getage()
----------------------------------
20

其实我们有时候希望有更简单的操作方式,如j.age来获取, j.age = 20来设置。
python 提供了 property 装饰器,property 装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
    
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, age):
        self.__age = age
    
    @age.deleter
    def age(self):
        del self.__age

j = Person('jerry')
print(j.age)
j.age = 20
print(j.age)
--------------
18
20
  • 使用 property 装饰器的时三个方法 (getter,setter,deleter) 同名
  • property 装饰器必须在前,setter、deleter 装饰器在后
  • @property后面跟的函数名就是以后的属性名,他就是 getter,这个必须有,此时保证属性可读
  • @属性名.setter接收两个参数,第一个是 self,第二个是将要赋值的值,此时属性可写
  • @属性名.deleter控制属性是否删除,很少用

property(fget=None, fset=None, fdel=None, doc=None)

  • property 的另一个表示方式,使用 property 类
  • 至少要包含 fget
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    def getage(self):
        return self.__age
    
    def setage(self,age):
        self.__age = age
        
    def delage(self):
        del self.__age

    age = property(getage,setage,delage,'age property') # 

j = Person('jerry')
print(j.age)
j.age = 20
print(j.age)
# 至少包含fget,且可用lambda表达式简化
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    #def getage(self):
    #    return self.__age
    
    # age = property(getage)
    age =  property(lambda self:self.__age)

j = Person('jerry')
print(j.age)

参考

  • magedu