Python不像C++等其它语言一样,有public、protected和private这样的权限限定词来确定方法和变量的访问权限。如果要使用类似私有的效果,只能通过下划线命名方式来实现一种伪私有。
没有下划线开头的函数和变量名都是公有的,可以任意访问和使用。
单下划线开头的函数名和变量名被当做模块内使用的内部函数和变量,不能通过from a_model import *导入,但使用import a_model导入,依然可以使用这些函数和变量。
类中双下划线开头的函数名和变量名实际上表示Python需要对名字进行变异(Name Mangling),之后在类外面通过这个名字是访问不到这些方法和函数的,从而实现了一种私有的形式。但是如果你知道了变异后的名字,依然可以访问这些方法和变量,所以Python的私有是一种伪私有。比如我们有如下类定义:
class C1:
name='' #一般的名字,公有
_name='' #模块内可以访问
__name #C1的私有类变量,类中才能访问
def __init__(self, n='C1'): self.name=self._name=self.__name=n
class C2(C1): #由C1派生出C2
name='' #一般类变量,重载C1的name
_name='' #模块内可以访问,重载C1的_name
__name='' #C2的私有类变量,类中才能访问
def __init__(self, n='C2'): self.name=self._name=self.__name=n
def print(self):
print(super().name) #打印C1的name
print(super()._name) #打印C1的_name
print(super().__name) #编译会发生错误,因为C1.__name是私有的不能访问
c2=C2()
c2.name #将会显示name的值
c2._name #虽然_name是内部变量,由于在模块内,会显示_name的值
c2.__name #由于__name为私有,所以会显示没有__name这个属性
其实我们可以通过调用c2.__dict__查看其字典,发现__name仅仅是被变异为了_类名__变量名,如C1的__name变异为了_C1__name,C2的__name变异为_C2__name,如果我们写c2._C2__name依然是可以访问的,所以说这种私有是伪私有。
为了访问类中的私有变量,我们可以像其它语言一样通过定义特定的方法来实现访问,如可以如下定义C1类:
class C1:
__name='' #私有类变量
def __init__(self, n='C1'): self.__name=n
def getName(self): return self.__name #获取值方法
def setName(self, n): self.__name=n #设置值方法
def delName(self): del self.__name #删除值方法
这样,我们就可以通过相应的方法完成对私有值的访问。
当然,Python也具有很多其它语言的定义存取器的方式来访问私有变量。Python是通过特性property类来处理的,如下:
class property(fget=None, fset=None, fdel=None, doc=None)
该类会返回一个property特性。其中,fget为存取器getter,fset为设置器setter,fdel为删除器。在相应的类中定义相应的方法,并申明property类实例返回特性,就可以完成对私有变量的访问了,如下:
class C1:
__name=''
def __init__(self, n='C1'): self.__name=n
def getName(self): return self.__name #获取值方法
def setName(self, n): self.__name=n #设置值方法
def delName(self): del self.__name #删除值方法
name = property(getName, setName, delName) #申明property实例返回特性name并完成相关操作函数设置
这样,我们就可以通过C1的name完成对私有变量__name的操作了,如下:
c1=C1()
print(c1.name) #打印__name,实际是通过getter存取器getName访问
c1.name=5 #设置__name,实际是通过setter设置器setName访问
del c1.name #删除__name,实际是通过deleter删除器delName访问
也就是说,对C1的实例对象c1,c1.name调用了存取器,c1.name=5调用了设置器,del c1.name调用了删除器。
一个property实例对象具有设置器setter、存取器getter、删除器deleter方法,利用它们可以方便地用修饰器完成对私有变量的访问,如下:
class C:
def __init__(self): self.__x=None
@property #利用property修饰器定义存取器
def x(self):
return self.__x
@x.setter #利用x的setter修饰器定义设置器
def x(self, x):
self.__x=x
@x.deleter #利用x的deleter修饰器定义删除器
def x(self):
del self.x
c=C()
c.x=[1,3,5] #利用设置器赋值
print(c.x) #利用存取器访问
del c.x #利用删除器删除
从上述代码还应注意,被修饰函数的函数名会成为同名的变量名,要定义设置器和删除器,必须先定义存取器。
同时注意到,Python类只具有私有变量和方法,而不像C++一样具有受保护protected类型,让派生类能够访问基类的受保护数据。如果想在派生类中访问父类的私有数据,只能在派生类中重新定义修饰器,并利用super()函数完成对基类私有数据的访问,如有上例C类的派生类D需要访问C的私有数据x,则需要如下编码:
class D(C):
@property #定义一个存取器访问C的x
def x(self): return super().x #访问父类的x
@x.setter #定义设置器访问C.x
def x(self, x): super(D,D).x.__set__(self,x) #访问父类的x的__set__方法
@x.deleter #定义删除器访问C.x
def x(self): super(D,D).x.__delete__(self) #访问父类的x的__delete__方法