Python中单、双下划线的区别总结


Posted in Python onDecember 01, 2017

前言

Python 的代码风格由 PEP 8 描述。这个文档描述了 Python 编程风格的方方面面。在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格。这样就易于阅读,易于在程序员之间交流。

我们大家在学习Python的时候,好像很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__这种。在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语言中的private、public的作用一样,但仔细深究,这不全是Python这样设计的初衷。

下面我们具体分析,话不多说了,来一起看看吧。

单下划线开头

我们经常看到方法或者属性前面加了单下划线,并认为它表示该方法或者属性是该类型(Python和Golang一样,不光类可以有方法,很多类型甚至基本类型也可以定义方法)的私有方法或属性。但其实在Python中不存在真正意义上的私有方法或者属性,前面加单下划线_只是表示你不应该去访问这个方法或者属性,因为它不是API的一部分。

举个例子:

Python
class BaseForm(StrAndUnicode):
 ...

 def _get_errors(self):
 "Returns an ErrorDict for the data provided for the form"
 if self._errors is None:
 self.full_clean()
 return self._errors

 errors = property(_get_errors)

该代码片段来自Django源码(django/forms/forms.py)。这段代码的设计就是errors属性是对外API的一部分,如果你想获取错误详情,应该访问errors属性,而不是(也不应该)访问_get_errors方法。

双下划线开头

之前很多人跟我说Python中双下划线开头表示私有,我在很多地方也见到这样的说法。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。

我们看个例子:

class A(object):
 
 def __method(self):
 print("I'm a method in class A")

 def method_x(self):
 print("I'm another method in class A\n")

 def method(self):
 self.__method()
 self.method_x()

class B(A):
 
 def __method(self):
 print("I'm a method in class B")

 def method_x(self):
 print("I'm another method in class B\n")


if __name__ == '__main__':
 
 print("situation 1:")
 a = A()
 a.method()

 b = B()
 b.method()

 print("situation 2:")
 # a.__method()
 a._A__method()

执行结果:

situation 1:
I'm a method in class A
I'm another method in class A

I'm a method in class A
I'm another method in class B

situation 2:
I'm a method in class A

这里有两个点需要注意:

A类中我们定义了__method()、method_x和method()三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()和method_x方法,但是从输出结果看,B对象调用method()方法时调用了其父类A的__method()方法和自己的method_x()方法。也就是说,__method()覆写没有生效,而method_x()覆写生效了。而这也正是Python设计双下划线开头的唯一目的。

这一点也可在Python官方说明中得到答案:https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables。

前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀_类名,所以就像situation 2下面的代码,虽然我们不能用a直接访问__method(),但却可以加上前缀去访问,即_A__method()。

开头结尾双下划线

一般来说像__this__这种开头结尾都加双下划线的方法表示这是Python自己调用的,你不要调用。比如我们可以调用len()函数来求长度,其实它后台是调用了__len__()方法。一般我们应该使用len,而不是直接使用__len__():

a = [1, 2, 3]
print(len(a)) 
print(a.__len__()) # 和上面等效

num = 10
print(num + 10)
print(num.__add__(10)) # 和上面等效

我们一般称__len__()这种方法为magic methods,一些操作符后台调用的也是也是这些magic methods,比如+后台调用的是__add__,-调用的是__sub__,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()会在对象的初始化时调用,__new__()会在构建一个实例的时候调用等等。下面我们看两个例子:

class CrazyNumber(object):
 def __init__(self, n): 
 self.n = n 
 def __add__(self, other): 
 return self.n - other 
 def __sub__(self, other): 
 return self.n + other 
 def __str__(self): 
 return str(self.n) 

num = CrazyNumber(10) 
print(num) # output is: 10
print(num + 5) # output is: 5
print(num - 20) # output is: 30

在上面这个例子中,我们覆写了+和-操作符,将他们的功能交换了。再看个例子:

class Room(object):
 def __init__(self): 
 self.people = [] 
 def add(self, person): 
 self.people.append(person) 
 def __len__(self): 
 return len(self.people)
 
room = Room() 
room.add("Igor") 
print len(room) # output is: 1

这个例子中,因为我们实现了__len__(),所以Room对象也可以使用len函数了。

所有此类的方法都在这里有说明:documentation.

结论

  • 使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。
  • 使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。
  • 当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如len、add、+、-、==等)时,你可以定义该类方法。
  • 当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。

注:本文大部分内容参考自Difference between _ , and __xx in Python .

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python urllib模块urlopen()与urlretrieve()详解
Nov 01 Python
Python发送Email方法实例
Aug 21 Python
python基础教程之循环介绍
Aug 29 Python
对比Python中__getattr__和 __getattribute__获取属性的用法
Jun 21 Python
Python用imghdr模块识别图片格式实例解析
Jan 11 Python
解决seaborn在pycharm中绘图不出图的问题
May 24 Python
python计算列表内各元素的个数实例
Jun 29 Python
解决Python设置函数调用超时,进程卡住的问题
Aug 08 Python
django-rest-swagger的优化使用方法
Aug 29 Python
python实现按键精灵找色点击功能教程,使用pywin32和Pillow库
Jun 04 Python
python 实现表情识别
Nov 21 Python
判断Python中的Nonetype类型
May 25 Python
从CentOS安装完成到生成词云python的实例
Dec 01 #Python
Django的分页器实例(paginator)
Dec 01 #Python
浅谈python装饰器探究与参数的领取
Dec 01 #Python
Python简单读取json文件功能示例
Nov 30 #Python
Python实现嵌套列表及字典并按某一元素去重复功能示例
Nov 30 #Python
Python实现的多线程同步与互斥锁功能示例
Nov 30 #Python
Python实现按特定格式对文件进行读写的方法示例
Nov 30 #Python
You might like
Netflix将与CLAMP、乙一以及冲方丁等6名知名制作人合伙展开原创动画计划!
2020/03/06 日漫
js下函数般调用正则的方法附代码
2008/06/22 PHP
Linux(CentOS)下PHP扩展PDO编译安装的方法
2016/04/07 PHP
PHP生成图像验证码的方法小结(2种方法)
2016/07/18 PHP
laravel 中如何使用ajax和vue总结
2017/08/16 PHP
javascript function调用时的参数检测常用办法
2010/02/26 Javascript
基于KMP算法JavaScript的实现方法分析
2013/05/03 Javascript
JQuery设置和去除disabled属性的5种方法总结
2013/05/16 Javascript
js Math 对象的方法
2013/09/01 Javascript
jQuery实现仿美橙互联两级导航菜单效果完整实例
2015/09/17 Javascript
快速学习JavaScript的6个思维技巧
2015/10/13 Javascript
javascript实现二级级联菜单的简单制作
2015/11/19 Javascript
JavaScript动态设置div的样式的方法
2015/12/26 Javascript
js仿3366小游戏选字游戏
2016/04/14 Javascript
JS/jQ实现免费获取手机验证码倒计时效果
2016/06/13 Javascript
详细探究ES6之Proxy代理
2016/07/22 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
2016/12/27 Javascript
vue封装第三方插件并发布到npm的方法
2017/09/25 Javascript
Vue + Elementui实现多标签页共存的方法
2019/06/12 Javascript
Nodejs中使用puppeteer控制浏览器中视频播放功能
2019/08/26 NodeJs
jQuery实现简易QQ聊天框
2020/02/10 jQuery
vue 需求 data中的数据之间的调用操作
2020/08/05 Javascript
用Python遍历C盘dll文件的方法
2015/05/06 Python
python提取包含关键字的整行数据方法
2018/12/11 Python
Python实现检测文件的MD5值来查找重复文件案例
2020/03/12 Python
django使用多个数据库的方法实例
2021/03/04 Python
英国著名的药妆网站:Escentual
2016/07/29 全球购物
迎新晚会邀请函
2014/02/01 职场文书
区域销售主管岗位职责
2014/06/15 职场文书
三好学生先进事迹材料
2014/08/28 职场文书
党委书记群众路线对照检查材料思想汇报
2014/10/04 职场文书
驳回起诉裁定书
2015/05/19 职场文书
故意伤害罪辩护词
2015/05/21 职场文书
协议书格式模板
2016/03/24 职场文书
基于Golang 高并发问题的解决方案
2021/05/08 Golang
ES6 解构赋值的原理及运用
2021/05/25 Javascript