Python中单下划线与双下划线的区别详解
本文深入解析Python中单下划线、双下划线开头及两端双下划线的具体含义。单下划线暗示非公开API,双下划线用于避免子类覆盖(名称修饰),而两端双下划线通常定义魔术方法以支持内置函数和操作符重载。文章结合Django源码及类继承示例,澄清了关于“私有”的常见误区,并提供实际代码对比。
前言
Python的代码风格规范由PEP 8文档定义。该文档涵盖了Python编程风格的各个层面,旨在确保不同开发者编写的代码在遵守规范的前提下保持最大程度的风格一致性,从而提升代码的可读性及团队间的交流效率。
在Python学习过程中,许多开发者对方法或属性前添加的单个或多个下划线感到困惑,尤其是像__this__这种两端带有双下划线的形式。在查阅相关资料前,许多人误以为这些下划线的作用类似于Golang中通过大小写控制可见性,或如其他语言中private、public关键字的功能。然而,深入分析后发现,Python的设计初衷并非完全如此。
接下来我们将具体分析这三种下划线的使用场景及含义。
单下划线开头
开发者常看到方法或属性前添加单下划线,并误以为这表示该方法或属性属于该类型(Python中不仅类,许多类型甚至基本类型均可定义方法)的私有成员。实际上,Python中并不存在严格意义上的私有方法或属性。在名称前添加单下划线_,仅是一种约定,提示开发者不应直接访问该成员,因为它不属于公共API的一部分。
以下代码片段摘自Django源码(django/forms/forms.py)。该设计表明errors属性是对外公开的API,若需获取错误详情,应访问errors属性,而非调用(也不应调用)_get_errors方法。
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()三个方法;随后定义继承自A的类B,并在B中覆写(override)了父类的__method()和method_x方法。但从输出结果可见,当B对象调用method()时,实际调用了父类A的__method()和B自己的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)。一些操作符后台调用的也是这些魔术方法,例如+后台调用__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内置对象一样使用内置函数或操作符(如len、add、+、-、==等)时,可以定义相应的类方法。
- 此外,有些属性仅在末尾加了下划线,这仅仅是为了避免命名与Python保留关键字冲突,并无特殊含义。
注:本文大部分内容参考自Difference between _ , and __xx in Python .
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对本站的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













