分享

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

 沙门空海 2018-09-24
1
2
3
4
5
6
7
8
9
10
11
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设计此的真正目的仅仅是为了避免子类覆盖父类的方法。

我们看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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()

执行结果:

1
2
3
4
5
6
7
8
9
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./dev/peps/pep-0008/#method-names-and-instance-variables

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

开头结尾双下划线

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

1
2
3
4
5
6
7
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__()会在构建一个实例的时候调用等等。下面我们看两个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

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

1
2
3
4
5
6
7
8
9
10
11
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保留关键字冲突,没有特殊含义。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多