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使用cPickle模块序列化实例
Sep 25 Python
django接入新浪微博OAuth的方法
Jun 29 Python
Python中 map()函数的用法详解
Jul 10 Python
Python正则表达式指南 推荐
Oct 09 Python
Python一个简单的通信程序(客户端 服务器)
Mar 06 Python
基于Python的PIL库学习详解
May 10 Python
python GUI库图形界面开发之PyQt5布局控件QHBoxLayout详细使用方法与实例
Mar 06 Python
借助Paramiko通过Python实现linux远程登陆及sftp的操作
Mar 16 Python
Python基于模块Paramiko实现SSHv2协议
Apr 28 Python
python自动化测试三部曲之request+django实现接口测试
Oct 07 Python
python help函数实例用法
Dec 06 Python
pytorch 如何使用batch训练lstm网络
May 28 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检测数组长度函数sizeof与count用法
2014/11/17 PHP
PHP异常处理浅析
2015/05/12 PHP
php+mysql开发中的经验与常识小结
2019/03/25 PHP
js新闻滚动 js如何实现新闻滚动效果
2013/01/07 Javascript
JavaScript中window、doucment、body的解释
2013/08/14 Javascript
非jQuery实现照片散落桌子上,单击放大的LightBox效果
2014/11/28 Javascript
完美实现八种js焦点轮播图(上篇)
2016/07/18 Javascript
基于BootStrap栅格栏系统完成网站底部版权信息区
2016/12/23 Javascript
获取IE浏览器Cookie信息的方法
2017/01/23 Javascript
小程序实现左右来回滚动字幕效果
2018/12/28 Javascript
js全屏事件fullscreenchange 实现全屏、退出全屏操作
2019/09/17 Javascript
Layer组件多个iframe弹出层打开与关闭及参数传递的方法
2019/09/25 Javascript
通过js随机函数Math.random实现乱序
2020/05/19 Javascript
对vuex中store和$store的区别说明
2020/07/24 Javascript
jquery实现简单自动轮播图效果
2020/07/29 jQuery
利用React高阶组件实现一个面包屑导航的示例
2020/08/23 Javascript
vue封装自定义指令之动态显示title操作(溢出显示,不溢出不显示)
2020/11/12 Javascript
微信小程序实现可拖动悬浮图标(包括按钮角标的实现)
2020/12/29 Javascript
[45:16]完美世界DOTA2联赛循环赛 IO vs FTD BO2第二场 11.05
2020/11/06 DOTA
Python连接SQLServer2000的方法详解
2017/04/19 Python
django-crontab实现服务端的定时任务的示例代码
2020/02/17 Python
jupyter lab文件导出/下载方式
2020/04/22 Python
PyCharm中如何直接使用Anaconda已安装的库
2020/05/28 Python
python3+openCV 获取图片中文本区域的最小外接矩形实例
2020/06/02 Python
使用pytorch实现论文中的unet网络
2020/06/24 Python
解决pytorch 保存模型遇到的问题
2021/03/03 Python
德国婴儿服装和婴儿用品购买网站:Baby Sweets
2019/12/08 全球购物
班队活动设计方案
2014/01/30 职场文书
实验教师岗位职责
2014/02/13 职场文书
社会体育专业大学生职业生涯规划书
2014/09/17 职场文书
税务干部群众路线教育实践活动对照检查材料
2014/09/20 职场文书
社区班子个人对照检查材料思想汇报
2014/10/07 职场文书
个人党性锻炼总结
2015/03/05 职场文书
工厂无线对讲系统解决方案
2022/02/18 无线电
日本十大血腥动漫,那些被禁播的动漫盘点
2022/03/21 日漫
xhunter1.sys可以删除嘛? win11提示xhunter1.sys驱动不兼容解决办法
2022/09/23 数码科技