本文最初发表于恋花蝶的博客(http://blog.csdn.net/lanphaday),欢迎转载,但必须保留此声明且不得用于商业目的。谢谢。
引子
我热情地邀请大家猜测下面这段程序的输出:
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()
初探
正确的答案是:
A.__private()
B.public()
如果您已经猜对了,那么可以不看我这篇博文了。如果你没有猜对或者心里有所疑问,那我的这篇博文正是为您所准备的。
一切由为什么会输出“A.__private()”开始。但要讲清楚为什么,我们就有必要了解一下Python的命名机制。
据 Python manual,变量名(标识符)是Python的一种原子元素。当变量名被绑定到一个对象的时候,变量名就指代这个对象,就像人类社会一样,不是吗?当变量名出现在代码块中,那它就是本地变量;当变量名出现在模块中,它就是全局变量。模块相信大家都有很好的理解,但代码块可能让人费解些。在这里解释一下:
代码块就是可作为可执行单元的一段Python程序文本;模块、函数体和类定义都是代码块。不仅如此,每一个交互脚本命令也是一个代码块;一个脚本文件也是一个代码块;一个命令行脚本也是一个代码块。
接下来谈谈变量的可见性,我们引入一个范围的概念。范围就是变量名在代码块的可见性。如果一个代码块里定义本地变量,那范围就包括这个代码块。如果变量定义在一个功能代码块里,那范围就扩展到这个功能块里的任一代码块,除非其中定义了同名的另一变量。但定义在类中的变量的范围被限定在类代码块,而不会扩展到方法代码块中。
迷踪
据上节的理论,我们可以把代码分为三个代码块:类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的私有变量轧压了。
探究
懂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()”吧!
真相
相信现在聪明的读者已经猜到答案了吧?如果你还没有想到,我给你个提示:真相跟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命名机制
python中双下划线的作用(1)所有以双下划线开头的成员是私有的(2)python对于私有变量是会进行扎压(mangling)的,扎压规则是原始定义:class A(): __function ...
- python—命名规范
文件名全小写,可使用下划线 包应该是简短的.小写的名字.如果下划线可以改善可读性可以加入.如mypackage. 模块与包的规范同.如mymodule. 类总是使用首字母大写单词串.如MyClass. ...
- 【python测试开发栈】—理解python深拷贝与浅拷贝的区别
内存的浅拷贝和深拷贝是面试时经常被问到的问题,如果不能理解其本质原理,有可能会答非所问,给面试官留下不好的印象.另外,理解浅拷贝和深拷贝的原理,还可以帮助我们理解Python内存机制.这篇文章将会通过 ...
- 理解Python的双下划线命名(转)
add by zhj:今天在学习SimpleHTTPServer的源代码时,看到了Python标准库SocketServer模块中有个BaseServer类,该类的__init__方法定义如下 def ...
- 理解Python的双下划线命名
引子 我热情地邀请大家猜测下面这段程序的输出: class A(object): def __init__(self): self.__private() ...
- Python反射机制理解
Python反射机制用沛齐老师总结的话说就是:利用字符串的形式去对象(模块)中操作(寻找)成员. getattr(object, name) object代表模块,name代表模块中的属性或成员,该函 ...
- 深入理解 Python 异步编程(上)
http://python.jobbole.com/88291/ 前言 很多朋友对异步编程都处于"听说很强大"的认知状态.鲜有在生产项目中使用它.而使用它的同学,则大多数都停留在知 ...
- 理解Java类加载机制(译文)
理解java类加载机制 你想写类加载器?或者你遇到了ClassCastException异常,或者你遇到了奇怪的LinkageError状态约束异常.应该仔细看看java类的加载处理了. 什么是类加载 ...
- 【Python&数据结构】 抽象数据类型 Python类机制和异常
这篇是<数据结构与算法Python语言描述>的笔记,但是大头在Python类机制和面向对象编程的说明上面.我也不知道该放什么分类了..总之之前也没怎么认真接触过基于类而不是独立函数的Pyt ...
随机推荐
- CTF Writeup 一个专门收集WP的网站
www.ctfwp.com 创建于2019-04-15 致力于收集网上公开writeup,方便大家学习.
- 【基本功】深入剖析Swift性能优化
简介 2014年,苹果公司在WWDC上发布Swift这一新的编程语言.经过几年的发展,Swift已经成为iOS开发语言的“中流砥柱”,Swift提供了非常灵活的高级别特性,例如协议.闭包.泛型等,并且 ...
- 切换 NPM 镜像源
转载:快速切换NPM源 我们介绍过cnpmjs.org和淘宝 npm 两个 NPM 镜像.除此之外,还有一些国外的 NPM 镜像.不同地区访问不同的镜像速度可能有差异,因此有时候需要切换 NPM 镜像 ...
- [BJOI2010]次小生成树
OJ题号: BZOJ1977.COGS2453 题目大意: 给你一个无向连通图,求严格次小生成树. 思路: 对于一般次小生成树,我们有一个结论:一般次小生成树一定可以通过替换掉最小生成树某一条边得到. ...
- JavaScript 继承和数组
前言 因为篇幅比较短,所以将JavaScript中的继承和数组进行统一写. 继承 当一个函数对象被创建的时候,Function构造器产生的函数对象会运行类似这样的代码: this.prototype ...
- php发送get、post请求的6种方法代码示例
本文主要展示了php发送get.post请求的6种方法的代码示例,分别为使用file_get_contents .fopen.fsockopen.curl来发送GET和POST请求,代码如下: 方法1 ...
- Codeforces Round #359 (Div. 1) A. Robbers' watch 暴力
A. Robbers' watch 题目连接: http://www.codeforces.com/contest/685/problem/A Description Robbers, who att ...
- matlab 乱七八糟求最大值~
w=rand(,) [a b]=size(w); x=; maxw=w(,); :a :b if w(k,d)>maxw maxw=w(k,d); end end end disp(['haha ...
- UVALive 5058 Counting BST 数学
B - Counting BST Time Limit:3000MS Memory Limit:0KB 64bit IO Format:%lld & %llu Submit S ...
- ROS知识(8)----CMakeLists.txt文件编写的理解
ROS(Indigo)编程必须要理解CMakeList.txt的编写规则,教程地址:catkin/CMakeLists.txt,官网有相关的教程,中文的翻译版本写的很不错,教程地址:ROS中的CMak ...