1 引入
面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起,听起来是不是很熟悉,
没错,我们之前所说的”整合“二字其实就是封装的通俗说法。除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实
现:隐藏与开放接口。
2 隐藏属性
Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都
会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式:
1 class Foo: 2 x = 1 3 __N = 10 # 变形为_Foo__N 4 5 def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形 6 self.__x = 10 # 变形为self._Foo__x 7 8 def __f1(self): # 变形为_Foo__f1 9 print('__f1 run') 10 11 def f2(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形 12 self.__f1() # 变形为self._Foo__f1() 13 14 15 print(Foo.__dict__)
这种变形需要注意的问题是:
1)在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如Foo._Foo__N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。
print(Foo._Foo__N) print(Foo._Foo__f1) >>> 10 >>> <function Foo.__f1 at 0x00000288C4DCCE18>
2)在类内部是可以直接访问双下滑线开头的属性的,比如self.__f1(),因为在类定义阶段类内部双下滑线开头的属性统一发生了变形。
obj = Foo() obj.f2() >>> __f1 run
3)变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。
obj = Foo() obj.__y = 30 print(obj.__dict__) >>> {'_Foo__x': 10, '__y': 30}
3 开放接口
定义属性就是为了使用,所以隐藏并不是目的 。
1)将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制
# 设计者:egon class People: def __init__(self, name): self.__name = name def get_name(self): # 通过该接口就可以间接地访问到名字属性 # print('小垃圾,不让看') print(self.__name) def set_name(self,val): if type(val) is not str: print('小垃圾,必须传字符串类型') return self.__name=val # 使用者:王鹏 obj = People('egon') # print(obj.name) # 无法直接用名字属性 # obj.get_name() # obj.set_name('EGON') # obj.set_name(123123123)
2)隐藏函数属性
目的的是为了隔离复杂度。