深入解析Python私有变量轧压机制与命名规则
本文深入解析Python的命名机制,重点讲解私有变量轧压(Private name mangling)原理。通过类定义、继承及代码生成过程,阐明以双下划线开头的变量如何被转换为_类名__变量名。文章结合具体代码示例,揭示子类覆盖私有方法时的行为差异及命名冲突风险,帮助开发者理解Python内部变量可见性与作用域规则。
请预测以下代码片段的运行输出结果:
class A(object):
def __init__(self):
self.__private()
self.public()
def __private(self):
print 'A.__private()'
def public(self):
print 'A.public()'
class B(A):
def __private(self):
print 'B.__private()'
def public(self):
print 'B.public()'
b = B()
1、初步分析
正确的输出结果应为:
A.__private()
B.public()
若您已准确猜中,可忽略本文;若存在疑惑或未能答对,本文将为您详细解答。
问题的核心在于为何会输出“A.__private()”。要彻底厘清这一现象,必须深入理解Python的变量命名机制。
根据Python manual的定义,变量名(即标识符)是Python中的基本原子元素。当变量名绑定至某一对象时,该变量名即指代该对象,这类似于现实世界中的命名规则。若变量名在代码块内定义,则为局部变量;若在模块层级定义,则为全局变量。模块概念通常易于理解,但“代码块”的概念可能较为抽象,此处予以说明:
代码块是指Python中可作为独立执行单元的一段程序文本。模块、函数体以及类定义均属于代码块。此外,每个交互式脚本命令、单个脚本文件以及命令行脚本也各自构成一个代码块。
接下来讨论变量的可见性,这涉及“范围”概念。范围决定了变量名在特定代码块中的可见程度。若在代码块内定义局部变量,其范围涵盖该代码块;若变量定义在函数代码块中,其范围可扩展至该函数内的任意子代码块,除非在子块中定义了同名变量。然而,定义在类中的变量范围仅限于类代码块,不会自动扩展到方法代码块中。
2、深入探讨
基于上述理论,可将代码划分为三个代码块:类A的定义、类B的定义以及变量b的赋值。根据类定义逻辑,类A包含三个成员变量(鉴于Python中函数也是对象,因此成员方法也可视为成员变量);类B包含两个成员变量。可通过以下代码进行验证:
>>> print '/n'.join(dir(A))
_A__private
__init__
public
>>> print '/n'.join(dir(B))
_A__private
_B__private
__init__
public
值得注意的是,类A中出现了名为_A__private的Attribute ,而原本的__private却消失了!这涉及到Python中的私有变量轧压机制。
3、原理解析
熟悉Python的读者应知晓,Python会将以两个或以上下划线开头且未以两个或以上下划线结尾的变量视为私有变量。在代码生成阶段,这些私有变量会被转换为长格式(即变为公有)。转换规则为:在变量名前插入类名,并在最前端添加一个下划线。此过程称为私有变量轧压(Private name mangling)。例如,类A中的__private标识符将被转换为_A__private,这解释了上一节中_A__private出现及__private消失的原因。
此外,补充两点相关说明:
- 首先,由于轧压会导致标识符长度增加,当长度超过
255时,Python会进行截断处理,需注意由此引发的命名冲突问题。 - 其次,当类名完全以下划线命名时,
Python将不再执行轧压操作。示例如下:
>>> class ____(object):
def __init__(self):
self.__method()
def __method(self):
print '____.__method()'
>>> print '/n'.join(dir(____))
__class__
__delattr__
__dict__
__doc__
__getattribute__
__hash__
__init__
__method # 没被轧压
__module__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__str__
__weakref__
>>> obj = ____()
____.__method()
>>> obj.__method() # 可以外部调用
____.__method()
现在,我们回过头来解释为何输出结果为“A.__private() ”。
4、最终真相
相信聪明的读者已推测出答案。若仍未想到,可参考以下提示:该机制与C语言中的宏预处理原理相似。
由于类A定义了一个私有成员函数(变量),在代码生成前会先执行私有变量轧压(请注意上一节中标红的代码行)。轧压后,类A的代码结构变为:
class A(object):
def __init__(self):
self._A__private() # 这行变了
self.public()
def _A__private(self): # 这行也变了
print 'A.__private()'
def public(self):
print 'A.public()'
这是否类似于C语言中的宏展开过程?
由于在类B定义时未覆盖__init__方法,因此调用的是A类的.__init__,,即执行self._A__private(),,从而自然输出“A.__private()””。
以下两段代码可进一步佐证并加深理解:
>>> class C(A):
def __init__(self): # 重写__init__,不再调用self._A__private
self.__private() # 这里绑定的是_C_private
self.public()
def __private(self):
print 'C.__private()'
def public(self):
print 'C.public()'
>>> c = C()
C.__private()
C.public()
############################
>>> class A(object):
def __init__(self):
self._A__private() # 调用一个没有定义的函数,Python会把它给我的 ^_^~
self.public()
def __private(self):
print 'A.__private()'
def public(self):
print 'A.public()'
>>>a = A()
A.__private()
A.public()
至此,关于Python命名机制的解析完毕。如需了解更多关于Python命名机制的内容,请查阅本站过往文章或继续浏览后续相关教程,感谢支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













