前段时间写了很久C++,回来再重新审视下python中的面向对象,又有了新的理解。

python中类中并没有像C++中的public、private等关键字来定义类成员的访问权限(也就是所谓的访问控制符),但是python中也是有私有和公有变量的,他们使用特殊的变量名称来实现隐藏效果的。

python通过下划线作为变量的前缀来指定特殊变量:

  • _xxx:不能通过from import *导入到当前空间中
  • __xxx__:python 内定的名称
  • __xxx:私有变量名 相当于C++中的private关键字的作用。

于是我自己写了个小例子理解了一下python的私有变量的大概机制,先上例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
_g = "private global var"

g = "public global var"


class A:
def __init__(self):
self.public_var = "A public_var"
self.__private_var = "A private_var"

def __private_func(self):
print "A private function."

def public_func(self):
print "A public function."

def pub_call_private(self):
self.__private_func()


class B(A):
def __init__(self):
A.__init__(self)

def __private_func(self):
print "B private function."

def b_pub_call_private(self):
self.__private_func()

在分析之前先回一下python中变量的双下划线和单下划线:

  1. 双下划线(__xxx)
    双下划线是python为类元素(类属性和类函数)的私有性提供的初步形式。python解释器在运行时候会对具有双下划线的变量进行“混淆”处理,也就是把具有双下划线的变量名称按照某种规则进行改变,这有改变以后用户直接调用原始的变量是无法访问的,因为这个变量在混淆处理以后根本就不存在。例如python如果看到了一个类函数的名称为__func(...)则就会处理成_Classname__func(...),这样如果用户直接调用__func根本就找不到这个变量,因为他改名了。
    通过加上类名的处理形成的新的名称可以防止父类和派生类变量名称的同名冲突。
    这里的处理有一点像C/C++预处理器宏展开,本身写程序的人在类中定义的变量或者函数名称为__xxx但是python会将其进行类似的展开处理成为_classname__xxx,像是单纯的纯文本操作。

  2. 单下划线(_xxx)
    这是针对简单的模块级私有化的,防止模块的属性使用from mymodule import *的形式加载模块私有化变量。

下面我针对自己写的这个例子来进行下python是如何实现数据封装的。

理解上面的规则来看这个例子就很清楚了。我先对其进行执行:

1
2
3
4
5
6
7
8
9
10
11
12
In [1]: from class_test import *

In [2]: g
Out[2]: 'public global var'

In [3]: _g
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-2458ddf91f55> in <module>()
----> 1 _g

NameError: name '_g' is not defined

这就说明了单下划线的作用了,是无法通过from class_test import *_g导入到当前空间中的。
当然如果使用如下的导入方式是可以的啦:
1
2
3
4
5
6
In [7]: class_test._g
Out[7]: 'private global var'

In [8]: from class_test import _g

In [9]:

好了下面测试下双下划线,为了好理解,我先把python处理的过程处理一遍,然后一切都清楚了,python的混淆处理会把我最初的代码处理成如下这样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A:
def __init__(self):
self.public_var = "A public_var"
self._A__private_var = "A private_var"

def _A__private_func(self):
print "A private function."

def public_func(self):
print "A public function."

def pub_call_private(self):
self._A__private_func()


class B(A):
def __init__(self):
A.__init__(self)

def _B__private_func(self):
print "B private function."

def b_pub_call_private(self):
self._B__private_func()

先看一下类变量和类方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [2]: dir(A)
Out[2]:
['_A__private_func',
'__doc__',
'__init__',
'__module__',
'pub_call_private',
'public_func']

In [3]: dir(B)
Out[3]:
['_A__private_func',
'_B__private_func',
'__doc__',
'__init__',
'__module__',
'b_pub_call_private',
'pub_call_private',
'public_func']

可见python已经进行了混淆处理。

然后我们进行实例化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
In [4]: a = A()

In [5]: dir(a)
Out[5]:
['_A__private_func',
'_A__private_var',
'__doc__',
'__init__',
'__module__',
'pub_call_private',
'public_func',
'public_var']

In [6]: b = B()

In [7]: dir(b)
Out[7]:
['_A__private_func',
'_A__private_var',
'_B__private_func',
'__doc__',
'__init__',
'__module__',
'b_pub_call_private',
'pub_call_private',
'public_func',
'public_var']

实例化后类方法与对象进行了绑定,同时初始化函数将变量也进行了初始化,私有变量也进行了混淆处理。

看一看继承相关

B是A的派生类,也就拥有A的公有方法,pub_call_private()public_func(),为了能说明问题我执行下pub_call_private()函数,
由于此函数中调用了私有变量,所以做过混淆处理,也就是真正执行的函数应该是这样的:

1
2
def pub_call_private(self):
self._A__private_func()

那么预期返回的值应该是:
1
A private function.

来看看结果:
1
2
3
4
In [8]: b.pub_call_private()
A private function.

In [9]:

恩,和我想的一样。其实私有变量是有继承给派生类B的,但是是以_A__private_func的名称进行传递的,所以如果要真正想查看A中的所谓私有数据是可行的。

A中的所有变量是通过B中的初始化函数传递的

在B的初始化函数中,我们调用了A类的未绑定函数来讲B对象的成员数据进行了初始化,如果我重载B的初始化函数但是不执行其父类A的初始化函数,那这样调用A类继承来的函数应该会找不到相应的变量了。我稍微做了下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def __init__(self):
self.public_var = "A public_var"
self.__private_var = "A private_var"

def pub_call_private(self):
print self.__private_var


class B(A):
def __init__(self):
pass


这样B中没有使用A的初始化函数来初始化自己的成员变量,但是公有方法pub_call_private()却被继承了过来,如果我使用B的实例调用继承来的这个函数,应该会找不到_A__private_var这个变量,因为B中并没有。
来测试下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [1]: from class_test import *

In [2]: b = B()

In [3]: b.pub_call_private()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-844e272f133a> in <module>()
----> 1 b.pub_call_private()

D:\Dropbox\temp\class_test.py in pub_call_private(self)
10
11 def pub_call_private(self):
---> 12 print self.__private_var
13
14

AttributeError: B instance has no attribute '_A__private_var'

果然不出所料!

总结

因此在这个例子中只要理解了python是如何进行混淆处理的,那么私有变量相关的东西就全部很清晰了。

Comments