當涉及變數和方法名稱時,單一底線前綴有一個約定俗成的含義。它是對程式設計師的提示 - 意味著Python社群一致認為它應該是什麼意思,但程式的行為不受影響。
下劃線前綴的意思是告知其他程式設計師:以單一底線開頭的變數或方法僅供內部使用。該約定在PEP 8中有定義。
這不是Python強制規定的。 Python不像Java在「私有」和「公有」變數之間有很強的區別。這就像有人提出了一個小小的下劃線警告標誌,說:
「嘿,這不是真的要成為類別的公共介面的一部分。不去管它就好。」
有時候,一個變數的最適合的名稱已經被一個關鍵字所佔用。因此,像class或def這樣的名稱不能用作Python中的變數名稱。在這種情況下,你可以附加一個底線來解決命名衝突:
>>> def make_object(name, class): SyntaxError: "invalid syntax" >>> def make_object(name, class_): ... pass
總之,單一末尾下劃線(後綴)是一個約定,用來避免與Python關鍵字產生命名衝突。 PEP 8解釋了這個約定。
到目前為止,我們所涉及的所有命名模式的含義,來自於已達成共識的約定。而對於以雙底線開頭的Python類別的屬性(包括變數和方法),情況就有點不同了。
雙底線前綴會導致Python解釋器重寫屬性名稱,以避免子類別中的命名衝突。
這也叫做名稱修飾(name mangling) - 解釋器更改變數的名稱,以便在類別被擴展的時候不容易產生衝突。
我知道這聽起來很抽象。因此,我組合了一個小小的程式碼範例來說明:
class Test: def __init__(self): self.foo = 11 self._bar = 23 self.__baz = 23
讓我們用內建的dir()函數來看看這個物件的屬性:
>>> 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']
以上是這個對象屬性的列表。讓我們來看看這個列表,並尋找我們的原始變數名稱foo,_bar
和__baz
- 我保證你會注意到一些有趣的變化。
self.foo變數在屬性清單中顯示為未修改為foo。 self._bar的行為方式相同 - 它以_bar
的形式顯示在類別上。就像我之前說過的,在這種情況下,前導下劃線只是一個約定。給程式設計師一個提示而已。然而,對於self.__baz
而言,情況看起來有點不同。當你在該列表中搜尋__baz
時,你會看不到有這個名字的變數。
__baz出什麼狀況了?
如果你仔細觀察,你會看到此物件上有一個名為_Test__baz
的屬性。這就是Python解釋器所做的名稱修飾。它這樣做是為了防止變數在子類別中被重寫。
讓我們建立另一個擴充Test類別的類,並嘗試重寫建構函式中新增的現有屬性:
class ExtendedTest(Test): def __init__(self): super().__init__() self.foo = 'overridden' self._bar = 'overridden' self.__baz = 'overridden'
現在,你認為foo,_bar
和__baz
的值會出現在這個ExtendedTest類別的實例上嗎?我們來看看:
>>> t2 = ExtendedTest() >>> t2.foo 'overridden' >>> t2._bar 'overridden' >>> t2.__baz AttributeError: "'ExtendedTest' object has no attribute '__baz'"
等一下,當我們嘗試查看t2 .__ baz
的值時,為什麼我們會得到AttributeError?名稱修飾被再次觸發了!事實證明,這個物件甚至沒有__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']
正如你可以看到__baz
變成_ExtendedTest__baz
#以防止意外修改:
>>> t2._ExtendedTest__baz 'overridden'
但原來的_Test__baz
#還在:
>>> t2._Test__baz 42
雙底線名稱修飾對程式設計師是完全透明的。下面的例子證實了這一點:
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'"
名稱修飾是否也適用於方法名稱?是的,也適用。名稱修飾會影響在一個類別的上下文中,以兩個底線字元("dunders")
開頭的所有名稱:
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
在這個例子中,我宣告了一個名為_MangledGlobal__mangled
的全域變數。然後我在名為MangledGlobal的類別的上下文中存取變數。由於名稱修飾,我能夠在類別的test()方法內,以__mangled
來引用_MangledGlobal__mangled
全域變數。
Python解釋器會自動將名稱__mangled
擴展為_MangledGlobal__mangled
,因為它以兩個底線字元開頭。這表示名稱修飾不是專門與類別屬性關聯的。它適用於在類別上下文中使用的兩個下劃線字元開頭的任何名稱。
有很多要吸收的內容吧。
老實說,這些例子和解釋不是從我腦中蹦出來的。我做了一些研究和加工才弄出來。我一直使用Python,有很多年了,但像這樣的規則和特殊情況並不總是浮現在腦海裡。
有時候程式設計師最重要的技能是“模式識別”,而且知道在哪裡查閱資訊。如果您在這一點上感到有點不知所措,請不要擔心。慢慢來,試試這篇文章中的一些例子。
让这些概念完全沉浸下来,以便你能够理解名称修饰的总体思路,以及我向您展示的一些其他的行为。如果有一天你和它们不期而遇,你会知道在文档中按什么来查。
_var_
也许令人惊讶的是,如果一个名字同时以双下划线开始和结束,则不会应用名称修饰。 由双下划线前缀和后缀包围的变量不会被Python解释器修改:
class PrefixPostfixTest: def __init__(self): self.__bam__ = 42 >>> PrefixPostfixTest().__bam__ 42
但是,Python保留了有双前导和双末尾下划线的名称,用于特殊用途。 这样的例子有,__init_
_对象构造函数,或__call__
— 它使得一个对象可以被调用。
这些dunder方法通常被称为神奇方法 - 但Python社区中的许多人(包括我自己)都不喜欢这种方法。
最好避免在自己的程序中使用以双下划线(“dunders”)开头和结尾的名称,以避免与将来Python语言的变化产生冲突。
按照习惯,有时候单个独立下划线是用作一个名字,来表示某个变量是临时的或无关紧要的。
例如,在下面的循环中,我们不需要访问正在运行的索引,我们可以使用“_”来表示它只是一个临时值:
>>> for _ in range(32): ... print('Hello, World.')
你也可以在拆分(unpacking)表达式中将单个下划线用作“不关心的”变量,以忽略特定的值。 同样,这个含义只是“依照约定”,并不会在Python解释器中触发特殊的行为。 单个下划线仅仅是一个有效的变量名称,会有这个用途而已。
在下面的代码示例中,我将汽车元组拆分为单独的变量,但我只对颜色和里程值感兴趣。 但是,为了使拆分表达式成功运行,我需要将包含在元组中的所有值分配给变量。 在这种情况下,“_”作为占位符变量可以派上用场:
>>> car = ('red', 'auto', 12, 3812.4) >>> color, _, _, mileage = car >>> color 'red' >>> mileage 3812.4 >>> _ 12
除了用作临时变量之外,“_”是大多数Python REPL中的一个特殊变量,它表示由解释器评估的最近一个表达式的结果。
这样就很方便了,比如你可以在一个解释器会话中访问先前计算的结果,或者,你是在动态构建多个对象并与它们交互,无需事先给这些对象分配名字:
>>> 20 + 3 23 >>> _ 23 >>> print(_) 23 >>> list() [] >>> _.append(1) >>> _.append(2) >>> _.append(3) >>> _ [1, 2, 3]
以上是Python中底線的意思及用法有哪些?的詳細內容。更多資訊請關注PHP中文網其他相關文章!