注:本文大部分内容参考自Difference between _, and __xx in Python.
在学习Python的时候,很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__
这种。在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语言中的private
、public
的作用一样,但仔细深究,这不全是Python这样设计的初衷。下面我们具体分析。
单下划线开头
我们经常看到方法或者属性前面加了单下划线,并认为它表示该方法或者属性是该类型(Python和Golang一样,不光类可以有方法,很多类型甚至基本类型也可以定义方法)的私有方法或属性。但其实在Python中不存在真正意义上的私有方法或者属性,前面加单下划线_
只是表示你不应该去访问这个方法或者属性,因为它不是API的一部分。举个例子:
Python
1 | 复制代码class BaseForm(StrAndUnicode): |
该代码片段来自Django源码(django/forms/forms.py)。这段代码的设计就是errors
属性是对外API的一部分,如果你想获取错误详情,应该访问errors
属性,而不是(也不应该)访问_get_errors
方法。
双下划线开头
之前很多人跟我说Python中双下划线开头表示私有,我在很多地方也见到这样的说法。这样理解可能也不能说错,但这不是Python设计双下划线开头的初衷和目的,Python设计此的真正目的仅仅是为了避免子类覆盖父类的方法。我们看个例子:
Python
1 | 复制代码class A(object): |
执行结果:
1 | 复制代码situation 1: |
这里有两个点需要注意:
- A类中我们定义了
__method()
、method_x
和method()
三个方法;然后我们重新定义一个类B,继承自A,并且在B类中覆写(override)了其父类的__method()
和method_x
方法,但是从输出结果看,B对象调用method()
方法时调用了其父类A的__method()
方法和自己的method_x()
方法。也就是说,__method()
覆写没有生效,而method_x()
覆写生效了。而这也正是Python设计双下划线开头的唯一目的。这一点也可在Python官方说明中得到答案:
www.python.org/dev/peps/pe…。 - 前面我们就说了,Python中不存在真正意义上的私有变量。对于双下划线开头的方法和属性虽然我们不能直接引用,那是因为Python默认在其前面加了前缀
_类名
,所以就像situation 2
下面的代码,虽然我们不能用a
直接访问__method()
,但却可以加上前缀去访问,即_A__method()
。
开头结尾双下划线
一般来说像__this__
这种开头结尾都加双下划线的方法表示这是Python自己调用的,你不要调用。比如我们可以调用len()
函数来求长度,其实它后台是调用了__len__()
方法。一般我们应该使用len
,而不是直接使用__len__()
:
Python
1 | 复制代码a = [1, 2, 3] |
我们一般称__len__()
这种方法为magic methods,一些操作符后台调用的也是也是这些magic methods,比如+
后台调用的是__add__
,-
调用的是__sub__
,所以这种机制使得我们可以在自己的类中覆写操作符(见后面例子)。另外,有的时候这种开头结尾双下划线方法仅仅是某些特殊场景的回调函数,比如__init__()
会在对象的初始化时调用,__new__()
会在构建一个实例的时候调用等等。下面我们看两个例子:
Python
1 | 复制代码class CrazyNumber(object): |
在上面这个例子中,我们覆写了+
和-
操作符,将他们的功能交换了。再看个例子:
Python
1 | 复制代码class Room(object): |
这个例子中,因为我们实现了__len__()
,所以Room
对象也可以使用len
函数了。
所有此类的方法都在这里有说明:documentation.
结论
- 使用单下划线(_one_underline)开头表示方法不是API的一部分,不要直接访问(虽然语法上访问也没有什么问题)。
- 使用双下划线开头(__two_underlines)开头表示子类不能覆写该方法。除非你真的知道你在干什么,否则不要使用这种方式。
- 当你想让自己定义的对象也可以像Python内置的对象一样使用Python内置的一些函数或操作符(比如
len
、add
、+
、-
、==
等)时,你可以定义该类方法。 - 当然还有些属性只在末尾加了但下划线,这仅仅是为了避免我们起的一些名字和Python保留关键字冲突,没有特殊含义。
本文转载自: 掘金