分享

理解Python的双下划线命名(转过来慢慢理解)

 方海龙的书馆 2014-12-10
  1. 引子   
  2. 我热情地邀请大家猜测下面这段程序的输出:  
  3. class A(object):  
  4.        def __init__(self):   
  5.               self.__private()   
  6.               self.public()   
  7.        def __private(self):   
  8.               print 'A.__private()'   
  9.        def public(self):   
  10.               print 'A.public()'   
  11. class B(A):  
  12.        def __private(self):   
  13.               print 'B.__private()'   
  14.        def public(self):   
  15.               print 'B.public()'   
  16. b = B()  
  17. 初探   
  18. 正确的答案是:  
  19. A.__private()  
  20. B.public()  
  21. 如果您已经猜对了,那么可以不看我这篇博文了。如果你没有猜对或者心里有所疑问,那我的这篇博文正是为您所准备的。  
  22. 一切由为什么会输出“A.__private()”开始。但要讲清楚为什么,我们就有必要了解一下Python的命名机制。  
  23. 据 Python manual,变量名(标识符)是Python的一种原子元素。当变量名被绑定到一个对象的时候,变量名就指代这个对象,就像人类社会一样,不是吗?当变 量名出现在代码块中,那它就是本地变量;当变量名出现在模块中,它就是全局变量。模块相信大家都有很好的理解,但代码块可能让人费解些。在这里解释一下:  
  24. 代码块就是可作为可执行单元的一段Python程序文本;模块、函数体和类定义都是代码块。不仅如此,每一个交互脚本命令也是一个代码块;一个脚本文件也是一个代码块;一个命令行脚本也是一个代码块。  
  25. 接下来谈谈变量的可见性,我们引入一个范围的概念。范围就是变量名在代码块的可见性。 如果一个代码块里定义本地变量,那范围就包括这个代码块。如果变量定义在一个功能代码块里,那范围就扩展到这个功能块里的任一代码块,除非其中定义了同名 的另一变量。但定义在类中的变量的范围被限定在类代码块,而不会扩展到方法代码块中。  
  26. 迷踪   
  27. 据上节的理论,我们可以把代码分为三个代码块:类A的定义、类B的定义和变量b的定义。根据类定义,我们知道代码给类A定义了三个成员变量(Python的函数也是对象,所以成员方法称为成员变量也行得通。);类B定义了两个成员变量。这可以通过以下代码验证:  
  28. >>> print '\n'.join(dir(A))  
  29. _A__private  
  30. __init__  
  31. public  
  32. >>> print '\n'.join(dir(B))  
  33. _A__private  
  34. _B__private  
  35. __init__  
  36. public  
  37. 咦,为什么类A有个名为_A__private的 Attribute 呢?而且__private消失了!这就要谈谈Python的私有变量轧压了。  
  38. 探究   
  39. 懂Python的朋友都知道Python把以两个或以上下划线字符开头且没有以两个或以上下划线结尾的变量当作私有变量。私有变量会在代码生成之前被转换为长格式(变为公有)。转换机制是这样的:在变量前端插入类名,再在前端加入一个下划线字符。这就是所谓的私有变量轧压(Private name mangling)。如类 A里的__private标识符将被转换为_A__private,这就是上一节出现_A__private和__private消失的原因了。  
  40. 再讲两点题外话:  
  41. 一是因为轧压会使标识符变长,当超过255的时候,Python会切断,要注意因此引起的命名冲突。  
  42. 二是当类名全部以下划线命名的时候,Python就不再执行轧压。如:  
  43. >>> class ____(object):  
  44.        def __init__(self):   
  45.               self.__method()   
  46.        def __method(self):   
  47.               print '____.__method()'   
  48. >>> print '\n'.join(dir(____))  
  49. __class__  
  50. __delattr__  
  51. __dict__  
  52. __doc__  
  53. __getattribute__  
  54. __hash__  
  55. __init__  
  56. __method              # 没被轧压   
  57. __module__  
  58. __new__  
  59. __reduce__  
  60. __reduce_ex__  
  61. __repr__  
  62. __setattr__  
  63. __str__  
  64. __weakref__  
  65. >>> obj = ____()  
  66. ____.__method()  
  67. >>> obj.__method()      # 可以外部调用   
  68. ____.__method()  
  69. 现在我们回过头来看看为什么会输出“A.__private()”吧!  
  70. 真相   
  71. 相信现在聪明的读者已经猜到答案了吧?如果你还没有想到,我给你个提示:真相跟C语言里的宏预处理差不多。  
  72. 因为类A定义了一个私有成员函数(变量),所以在代码生成之前先执行私有变量轧压(注意到上一节标红的那行字没有?)。轧压之后,类A的代码就变成这样了:  
  73. class A(object):  
  74.        def __init__(self):   
  75.               self._A__private()          # 这行变了   
  76.               self.public()   
  77.        def _A__private(self):           # 这行也变了   
  78.               print 'A.__private()'   
  79.        def public(self):   
  80.               print 'A.public()'   
  81. 是不是有点像C语言里的宏展开啊?  
  82. 因为在类B定义的时候没有覆盖__init__方法,所以调用的仍然是A.__init__,即执行了self._A__private(),自然输出“A.__private()”了。  
  83. 下面的两段代码可以增加说服力,增进理解:  
  84. >>> class C(A):  
  85.        def __init__(self):          # 重写 __init__ ,不再调用 self._A__private   
  86.               self.__private()       # 这里绑定的是 _C_private   
  87.               self.public()   
  88.        def __private(self):   
  89.               print 'C.__private()'   
  90.        def public(self):   
  91.               print 'C.public()'   
  92. >>> c = C()  
  93. C.__private()  
  94. C.public()  
  95. ############################  
  96. >>> class A(object):  
  97.        def __init__(self):   
  98.               self._A__private()   # 调用一个没有定义的函数, Python 会把它给我的    
  99.               self.public()   
  100.        def __private(self):   
  101.               print 'A.__private()'   
  102.        def public(self):   
  103.               print 'A.public()'   
  104. >>>a = A()  
  105. A.__private()  
  106. A.public()  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多