譯者 | 泰然
連結 | https://dbader.org/blog/meaning-of-underscores-in-python
本文介紹了Python中單下劃線和雙下劃線("dunder")的各種含義和命名約定,名稱修飾(name mangling)的工作原理,以及它如何影響你自己的Python類。單前導下劃線:_var
單末尾下劃線:var_
雙前導下劃線:__var
雙前導和末尾下劃線:__var__
單下劃線:_
在文章結尾處,你可以找到一個簡短的「速查表」,總結了五種不同的下劃線命名約定及其含義,以及一個簡短的視頻教程,可讓你親身體驗它們的行為。 1. 單前導下劃線 _var當涉及到變量和方法名稱時,單個下劃線前綴有一個約定俗成的含義。 它是對程式設計師的一個提示 - 意味著Python社區一致認為它應該是什麼意思,但程序的行為不受影響。下劃線前綴的含義是告知其他程式設計師:以單個下劃線開頭的變量或方法僅供內部使用。 該約定在PEP 8中有定義。這不是Python強制規定的。Python不像Java那樣在「私有」和「公共」變量之間有很強的區別。 這就像有人提出了一個小小的下劃線警告標誌,說:「嘿,這不是真的要成為類的公共接口的一部分。不去管它就好。「class Test:
def __init__(self):
self.foo = 11
self._bar = 23
>>> t = Test()
>>> t.foo
11
>>> t._bar
23
# This is my_module.py:
def external_func():
return 23
def _internal_func():
return 42
>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
NameError: "name '_internal_func' is not defined"
>>> import my_module
>>> my_module.external_func()
23
>>> my_module._internal_func()
42
>>> def make_object(name, class):
SyntaxError: "invalid syntax"
>>> def make_object(name, class_):
... pass
class Test:
def __init__(self):
self.foo = 11
self._bar = 23
self.__baz = 23
>>> t = Test()
>>> dir(t)
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', '_bar', 'foo']
self.foo變量在屬性列表中顯示為未修改為foo。
self._bar的行為方式相同 - 它以_bar的形式顯示在類上。 就像我之前說過的,在這種情況下,前導下劃線僅僅是一個約定。 給程式設計師一個提示而已。
然而,對於self.__baz而言,情況看起來有點不同。 當你在該列表中搜索__baz時,你會看不到有這個名字的變量。
如果你仔細觀察,你會看到此對象上有一個名為_Test__baz的屬性。 這就是Python解釋器所做的名稱修飾。 它這樣做是為了防止變量在子類中被重寫。讓我們創建另一個擴展Test類的類,並嘗試重寫構造函數中添加的現有屬性:class ExtendedTest(Test):
def __init__(self):
super().__init__()
self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'
>>> t2 = ExtendedTest()
>>> t2.foo
'overridden'
>>> t2._bar
'overridden'
>>> t2.__baz
AttributeError: "'ExtendedTest' object has no attribute '__baz'"
>>> dir(t2)
['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_bar', 'foo', 'get_vars']
>>> t2._ExtendedTest__baz
'overridden'
class ManglingTest:
def __init__(self):
self.__mangled = 'hello'
def get_mangled(self):
return self.__mangled
>>> ManglingTest().get_mangled()
'hello'
>>> ManglingTest().__mangled
AttributeError: "'ManglingTest' object has no attribute '__mangled'"
class MangledMethod:
def __method(self):
return 42
def call_it(self):
return self.__method()
>>> MangledMethod().__method()
AttributeError: "'MangledMethod' object has no attribute '__method'"
>>> MangledMethod().call_it()
42
_MangledGlobal__mangled = 23
class MangledGlobal:
def test(self):
return __mangled
>>> MangledGlobal().test()
23
class PrefixPostfixTest:
def __init__(self):
self.__bam__ = 42
>>> PrefixPostfixTest().__bam__
42
>>> for _ in range(32):
... print('Hello, World.')
>>> car = ('red', 'auto', 12, 3812.4)
>>> color, _, _, mileage = car
>>> color
'red'
>>> mileage
3812.4
>>> _
12
>>> 20 + 3
23
>>> _
23
>>> print(_)
23
>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]