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中使用M2Crypto模块实现AES加密的教程
Apr 08 Python
Python字符和字符值(ASCII或Unicode码值)转换方法
May 21 Python
Linux中安装Python的交互式解释器IPython的教程
Jun 13 Python
Python操作MongoDB详解及实例
May 18 Python
python 3.6 +pyMysql 操作mysql数据库(实例讲解)
Dec 20 Python
python OpenCV学习笔记直方图反向投影的实现
Feb 07 Python
python爬虫爬取淘宝商品信息
Feb 23 Python
Python根据当前日期取去年同星期日期
Apr 14 Python
python命令 -u参数用法解析
Oct 24 Python
opencv3/C++图像像素操作详解
Dec 10 Python
python实现批量转换图片为黑白
Jun 16 Python
解决pip安装tensorflow中出现的no module named tensorflow.python 问题方法
Feb 20 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
PHP 图片上传代码
2011/09/13 PHP
如何使用微信公众平台开发模式实现多客服
2016/01/06 PHP
Laravel框架表单验证操作实例分析
2019/09/30 PHP
BOOM vs RR BO5 第一场 2.14
2021/03/10 DOTA
Array.slice()与Array.splice()的返回值类型
2006/10/09 Javascript
Javascript之文件操作
2007/03/07 Javascript
防止页面被iframe(兼容IE,Firefox火狐)
2010/07/04 Javascript
扩展jquery实现客户端表格的分页、排序功能代码
2011/03/16 Javascript
jQuery截取指定长度字符串的实现原理及代码
2014/07/01 Javascript
JQuery仿小米手机抢购页面倒计时效果
2014/12/16 Javascript
8个超实用的jQuery功能代码分享
2015/01/08 Javascript
Angularjs 滚动加载更多数据
2016/03/17 Javascript
基于d3.js实现实时刷新的折线图
2016/08/03 Javascript
jquery自定义插件结合baiduTemplate.js实现异步刷新(附源码)
2016/12/22 Javascript
JavaScript实现公历转农历功能示例
2017/02/13 Javascript
Nodejs模块载入运行原理
2018/02/23 NodeJs
Python实现的弹球小游戏示例
2017/08/01 Python
python中的计时器timeit的使用方法
2017/10/20 Python
Python 一句话生成字母表的方法
2019/01/02 Python
python 搭建简单的http server,可直接post文件的实例
2019/01/03 Python
Python实现字符串中某个字母的替代功能
2019/10/21 Python
Python搭建HTTP服务过程图解
2019/12/14 Python
matlab灰度图像调整及imadjust函数的用法详解
2020/02/27 Python
python sklearn包——混淆矩阵、分类报告等自动生成方式
2020/02/28 Python
基于Python3读写INI配置文件过程解析
2020/07/23 Python
html5 sessionStorage会话存储_动力节点Java学院整理
2017/07/06 HTML / CSS
美国第二大团购网站:LivingSocial
2016/07/24 全球购物
Uber Eats台湾:寻找附近提供送餐服务的餐厅
2018/05/07 全球购物
医药代表个人的求职信分享
2013/12/08 职场文书
护理专业大学生自我推荐信
2014/01/25 职场文书
应届护士求职信范文
2014/01/26 职场文书
学习十八大标语
2014/10/09 职场文书
党的群众路线教育实践活动个人对照检查材料(四风)
2014/11/05 职场文书
期末个人总结范文
2015/02/13 职场文书
医学生自荐信范文(2016精选篇)
2016/01/28 职场文书
Java实现注册登录跳转
2022/06/16 Java/Android