3.2.3 私有属性的典型错误用法

在类里,出于封装的考虑往往会把一些实现细节定义成私有属性,而外部的实例无法直接访问这些私有属性。如果外部实例需要访问私有属性,则必须通过类提供的get和set方法。

在使用Python类的私有属性时,如果不了解私有属性的命名细节,就很容易用错。在如下的CarClassBadUsage.py案例中,在给出错误用法的同时会解释出错的原因,并在此基础上给出针对私有属性的正确用法。


01 # coding=utf-8
02 class Car:
03     # 私有属性,是否保险
04     __Insured = False
05     # 针对私有属性的get和set方法
06     def get_Insured(self):
07         return self.__Insured
08     def set_Insured(self,Insured):
09         self.__Insured = Insured
10 # 实例化类并使用
11 myCar = Car()
12 myCar.set_Insured(False)
13 myCar.__Insured=True
14 print(myCar.get_Insured()) # 输出False
15 print(myCar.__Insured)  # 输出True
16 # 等同于myCar.set_Insured(True),不建议这样做
17 myCar._Car__Insured=True
18 print(myCar.get_Insured()) # 输出True

在第2行到第9行定义的Car类里,我们在第4行定义了一个私有属性__Insured,并在第6行和第8行分别定义了针对该私有属性的get和set方法。

在第11行,我们用Car类初始化了myCar实例,并在第12行通过set_Insured方法设置了私有属性__Insured的值。

迷惑点在第13行,刚才已经提到,实例对象是无法访问或修改私有属性的,但为什么这里还能直接通过myCar对象来修改__Insured的值?原因是这里修改的__Insured并不是Car类里的私有属性__Insured。

比如我们通过第14行的get方法来访问私有属性__Insured,依然是False,并没有因第13行的设置而改变值。而且,在Car里的私有属性__Insured会被重命名为_Car__Insured,比如第17行的代码就直接通过_Car__Insured重命名了私有属性__Insured的值,相当于set动作。重命名完成后,通过第18行的get方法能验证更改后的效果。

从中我们能总结出下面3条关于私有属性的使用经验:

第一,对于类里的私有属性,一般需要提供get和set方法,以供外部实例读取和修改它的值。

第二,私有属性会出现重命名的现象,外部实例其实也能通过重命名后的私有属性名读取和修改类里的私有属性,但是不建议这样做。建议通过类提供的get和set方法进行读取操作。

第三,不要通过类似myCar.__Insured的操作来读写私有属性,因为这样的话操作的并不是私有属性,而是另一个公有属性。

最后再强调一下,这里提到了对私有属性的命名规则,并不是让大家通过变更后的名字去读写私有属性,因为这会破坏类的封装性,从而增加代码的复杂度。相反,这里讲述命名规则的目的是让大家避免用类似myCar.__Insured的形式去读写私有属性,因为指向的并不是预料中的私有属性。如果要读写私有属性,应该调用其对应的get和set方法。