在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,将某些部分“隐藏”起来,在程序外部“看不到”,其含义是其他程序无法调用,不是人用眼睛看不到那个代码。如果让代码变成人难以阅读和理解的形式,这种行为称作“代码混淆”(obfuscation)。
Python 中的下划线是一种含义很丰富的符号。
此前的内容中,已经使用过下划线( _
),比如变量名称如果是由两个单词构成,中间用下划线连接;再比如类的初始化方法 __init__()
是以双下划线开始和结束。现在探讨对象的封装,也可以用下划线实现,方式非常简单,即在准备封装的对象名字前面加“双下划线”。例如:
>>> class Foo:
... __name = "laoqi"
... book = 'python'
...
>>> f = Foo()
>>> f.book
'python'
>>> f.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__name'
在类 Foo
中有两个类属性,__name
是用双下划线开始命名的类属性;book
是通常见到的类属性命名。
创建实例 f
,f.book
能正确地显示属性的值;但是,f.__name
则显示了 AttributeError
异常。这说明在类 Foo
之外,无法调用 __name
属性。
>>> Foo.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '__name'
>>> hasattr(Foo, "__name")
False
>>> hasattr(Foo, "book")
True
除了用实例无法调用 __name
属性,用类名称 Foo
也无法调用。在类的外部检测 Foo
类是否具有 __name
属性时,返回了 False
,而检测 book
属性,则返回了 True
。与 book
相比,__name
就被“隐藏”了起来,不论是通过实例名称还是类名称,都无法调用它。
>>> class Foo:
... __name = "laoqi"
... book = 'python'
... def get_name(self):
... return Foo.__name
...
再给类 Foo
增加一个方法 get_name
,在这个方法中,通过类名称调用 __name
属性。
>>> f = Foo()
>>> f.get_name()
'laoqi'
再次实例化之后,执行 f.get_name()
后返回了类属性 __name
的值,但此属性是在类内部的方法中被调用的。
在 Python 中以双下划线开始命名的属性或方法,都会像 __name
那样,只能在类内部调用,在外部无法调用。将这种行为称为私有化(Private),亦即实现了对该名称所引用对象的封装。
下面的代码是一个比较完整的示例,请读者认真阅读,并体会“私有化”的作用效果。
# coding=utf-8
'''
filename: private.py
'''
class ProtectMe:
def __init__(self):
self.me = "qiwsir"
self.__name = "laoqi"
def __python(self):
print("I love Python.")
def code(self):
print("What language do you like?")
self.__python()
if __name__ == "__main__":
p = ProtectMe()
p.code()
print(p.me)
p.__python()
执行程序,看看效果:
% python private.py
What language do you like?
I love Python.
qiwsir
Traceback (most recent call last):
File "/Users/qiwsir/Documents/my_books/codes/private.py", line 21, in <module>
p.__python()
AttributeError: 'ProtectMe' object has no attribute '__python'
执行到 p.__python()
时报 AttributeError
异常,说明方法 __python()
不能调用,因为它的名称用双下划线开始,表明是一个私有化的方法。在 code()
方法内,调用了 __python()
方法,在执行 p.code()
时得到了正确结果,再次表明被封装的对象只能在类的内部调用。
那么,为什么在命名属性或方法时,以双下划线开始就能实现封装呢?其原因在于,Python 解释器会对以这种形式命名的对象重命名,在原来的名称前面增加前缀形如 _ClassName
的前缀。以在交互模式中创建的 Foo
类为例:
>>> Foo.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '__name'
>>> Foo._Foo__name # (1)
'laoqi'
Foo
的类属性 __name
被封装,其实是被 Python 解释器重命名为 _Foo__name
( Foo
前面是单下划线),若改用注释(1)形式,就可以得到 Foo
类的私有化类属性 __name
的值。
若在 Foo
类内调用 __name
,相当于正在写类内部代码块,类对象尚未经 Python 解释器编译。当类的代码块都编写完毕,Python 解释器将其中所有的 __name
都更名为 _Foo__name
,即可顺利调用其引用的对象。
而在类外面执行 Foo.__name
时,Python 解释器没有也不会将 __name
解析为 _Foo__name
,所以在调用__name
时就显示 AttributeError
。
读者在这里又看到了另外一种符号:单下划线。
在有的 Python 资料中,并不将上述的方式称为“私有化”——本质是改个名称嘛。而是用单下划线,“约定”该名称引用的对象作为私有化对象——注意是“约定”。
>>> class Bar:
... _name = "laoqi"
... def get_name(self):
... return self._name
...
这里约定 _name
只在类内部调用。诚然,如果你不履约,施行“霸权主义”,Python 也不惩戒该行为——没有抛出异常。
>>> Bar._name
'laoqi'
因此,也有的开发者认为 Python 并不支持真正的私有化,不能强制某对象私有化。于是将“单下划线”视为该对象宜作为内部使用的标记符。
以上两种私有化的观点和写法,此处并不强制读者采信哪一种,因为这都是编程的形式——不用这些也能写程序。