python 類中函數(shù)名前后加下劃線的具體使用
在Python編程語言中,函數(shù)名前后有下劃線是一種常見的命名約定,被廣泛應用于類中的函數(shù)。這種命名約定被稱為“下劃線命名風格”或“Snake Case”,它有助于提高代碼的可讀性和可維護性。本文將介紹下劃線命名風格的由來、使用場景以及如何正確應用它。
下劃線命名風格的由來
下劃線命名風格起源于Python社區(qū)的一種共識,旨在使代碼更易讀、易理解、易維護。在Python社區(qū)中,強調代碼的可讀性,并推崇一致性和語義明確的命名風格。下劃線命名風格是一種約定俗成的規(guī)范,廣泛應用于Python編程中。
下劃線命名風格的核心思想是通過在函數(shù)名前后添加下劃線來增強函數(shù)的可讀性。這種命名方式使得函數(shù)名更加清晰、易于理解,并且能夠與其他命名方式進行區(qū)分。
使用下劃線命名風格的場景
下劃線命名風格適用于類中的函數(shù)、方法和變量。下面是一些常見的使用場景:
1. 類中的私有函數(shù)和變量
在Python中,沒有嚴格的私有函數(shù)和變量的概念。然而,通過在函數(shù)名前面添加一個下劃線,可以向其他開發(fā)者傳達出這個函數(shù)或變量是類內部使用的信號。這樣做有助于防止其他開發(fā)者在未經允許的情況下修改或訪問這些函數(shù)和變量。
class MyClass: def _private_function(self): # 這是一個私有函數(shù),只能在類內部訪問 pass def public_function(self): # 這是一個公共函數(shù),可以從類外部訪問 pass
2. 類中的保護函數(shù)和變量
在Python中,通過在函數(shù)名前面添加雙下劃線(例如__function_name)可以創(chuàng)建受保護的函數(shù)和變量。這意味著這些函數(shù)和變量只能在類內部或派生類中訪問,而不能從類的外部訪問。
class MyClass: def __protected_function(self): # 這是一個受保護的函數(shù),只能在類內部或派生類中訪問 pass def public_function(self): # 這是一個公共函數(shù),可以從類外部訪問 pass
3. 避免命名沖突
在大型項目中,可能存在許多不同的模塊和類。通過在函數(shù)名前面添加類名或模塊名作為前綴,可以避免命名沖突,并使函數(shù)的作用范圍更加明確。
class MyClass: def my_class_function(self): # 這是屬于 MyClass 類的函數(shù) pass class AnotherClass: def my_class_function(self): # 這是屬于 AnotherClass 類的函數(shù) pass
下劃線命名風格的注意事項
雖然下劃線命名風格可以提高代碼的可讀性和可維護性,但在使用時也需要注意以下幾點:
1. 遵循PEP 8標準
PEP 8是Python代碼風格指南,提供了一套一致性和規(guī)范性的編碼約定。在使用下劃線命名風格時,應遵循PEP 8標準,以確保代碼的一致性和易讀性。
2. 不要濫用下劃線
下劃線命名風格應該僅用于適當?shù)膱鼍?,如私有函?shù)、保護函數(shù)和避免命名沖突。濫用下劃線可能導致代碼的冗余和混亂,降低代碼的可讀
補:五種下劃線模式
單前導下劃線:_var
Python中并沒有真正意義上的“私有”,類的屬性的的可見性取決于屬性(變量和方法)的名字。
以單下劃線開頭的屬性(例如_spam),應被當成API中非公有的部分(但是注意,它們仍然可以被訪問),一般是具體實現(xiàn)細節(jié)的部分,修改它們時無需對外部通知,需要提供外部接口以供外部調用。在類中,帶有前導下劃線的名稱只是向其他程序員指示該屬性或方法旨在是私有的。
引用PEP-8:
_single_leading_underscore: weak “internal use” indicator. E.g.
from M import *
does not import objects whose name starts with an underscore.
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方法。
(1)約定的內部變量
當涉及到變量和方法名稱時,單個下劃線前綴有一個約定俗成的含義。 它是對程序員的一個提示 ,意味著Python社區(qū)一致認為它應該是什么意思,但程序的行為不受影響。下劃線前綴的含義是告知其他程序員:以單個下劃線開頭的變量或方法僅供內部使用。 該約定在PEP 8中有定義。但這不是Python強制規(guī)定的。 Python不像Java那樣在“私有”和“公共”變量之間有很強的區(qū)別。
看看下面的例子:
class Test: def __init__(self): self.foo = 11 self._bar = 23
如果你實例化此類,并嘗試訪問在__init__構造函數(shù)中定義的foo和_bar屬性,會發(fā)生什么情況? 讓我們來看看:
>>> t = Test() >>> t.foo 11 >>> t._bar 23
你會看到_bar中的單個下劃線并沒有阻止我們“進入”類并訪問該變量的值。這是因為Python中的單個下劃線前綴僅僅是一個約定,而不是一個強制性的規(guī)則。至少相對于變量和方法名而言。
其可以直接訪問。不過根據(jù)python的約定,應該將其視作private,而不要在外部使用它們,良好的編程習慣是不要在外部使用它。
(2)模塊級私有化
單下劃線常用來實現(xiàn)模塊級私有化,當我們使用“from mymodule import *”來加載模塊的時候,不會加載以單下劃線開頭的模塊屬性。也就是前導下劃線的確會影響從模塊中導入名稱的方式。
假設你在一個名為my_module的模塊中有以下代碼:
# This is my_module.py: def external_func(): return 23 def _internal_func(): return 42
現(xiàn)在,如果使用通配符從模塊中導入所有名稱,則Python不會導入帶有前導下劃線的名稱(除非模塊定義了覆蓋此行為的__all__列表,模塊或包中的__all__列表顯式地包含了他們):
>>> from my_module import * >>> external_func() 23 >>> _internal_func() NameError: "name '_internal_func' is not defined"
順便說一下,應該避免通配符導入,因為它們使名稱空間中存在哪些名稱不清楚,存在重名的變量會出現(xiàn)混亂。 為了清楚起見,堅持常規(guī)導入更好。
與通配符導入不同,常規(guī)導入不受前導單個下劃線命名約定的影響:
>>> import my_module >>> my_module.external_func() 23 >>> my_module._internal_func() 42
我知道這一點可能有點令人困惑。 如果你遵循PEP 8推薦,避免通配符導入,那么你真正需要記住的只有這個:
單個下劃線是一個Python命名約定,表示這個名稱是供內部使用的。 它通常不由Python解釋器強制執(zhí)行,僅僅作為一種對程序員的提示。
單末尾下劃線:var_
有時候,一個變量的最合適的名稱已經被一個關鍵字所占用。 因此,像class或def這樣的名稱不能用作Python中的變量名稱。 在這種情況下,你可以附加一個下劃線來解決命名沖突:
>>> def make_object(name, class): SyntaxError: "invalid syntax" >>> def make_object(name, class_): ... pass
總之,單個末尾下劃線(后綴)是一個約定,用來避免與Python關鍵字產生命名沖突。 PEP 8解釋了這個約定。
雙前導下劃線:__var
雙下劃線用在Python類變量中是Name Mangling。這種特定的行為差不多等價于Java中的final方法(不能被擠成)和C++中的正常方法(非虛方法)。之前很多人說Python中雙下劃線開頭表示私有。這樣理解可能也不能說錯,但這不是Python設計雙下劃線開頭的初衷和目的,Python設計此的真正目的僅僅是為了避免子類覆蓋父類的方法。
《Python學習手冊》的說明,以雙下劃線開頭的變量名,會自動擴張(原始的變量名會在頭部加入一個下劃線,然后是所在類名稱),從而包含了所在類的名稱。無法被繼承,也無法在外部訪問。必須通過改寫后的變量名或函數(shù)名來訪問。更像是私有變量和私有函數(shù)。當然也不是靜態(tài)變量。
(1)防止子類重寫父類屬性
以雙下劃線開頭并最多以一個下劃線結尾的變量或函數(shù)(例如___spam),將會被替換成_classname__spam這樣的形式,其中classname是當前類的類名。知道了這點之后,我們仍然可以訪問到python的私有函數(shù)
Any identifier of the form
__spam
(at least two leading underscores, at most one trailing underscore) is textually replaced with_classname__spam
, whereclassname
is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, so it can be used to define class-private instance and class variables, methods, variables stored in globals, and even variables stored in instances. private to this class on instances of other classes.
和來自同一頁面的警告:
Name mangling is intended to give classes an easy way to define “private” instance variables and methods, without having to worry about instance variables defined by derived classes, or mucking with instance variables by code outside the class. Note that the mangling rules are designed mostly to avoid accidents; it still is possible for a determined soul to access or modify a variable that is considered private.
到目前為止,我們所涉及的所有命名模式的含義,來自于已達成共識的約定。 而對于以雙下劃線開頭的Python類的屬性(包括變量和方法),情況就有點不同了。
雙下劃線前綴會導致Python解釋器重寫屬性名稱,以避免子類中的命名沖突。這也叫做名稱修飾(name mangling) - 解釋器更改變量的名稱,以便在類被擴展的時候不容易產生沖突。
我知道這聽起來很抽象。 因此,我組合了一個小小的代碼示例來予以說明:
class Test: def __init__(self): self.foo = 11 self._bar = 23 self.__baz = 23
讓我們用內置的dir()函數(shù)來看看這個對象的屬性:
>>> 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解釋器所做的名稱修飾。 它這樣做是為了防止變量在子類中被重寫。
讓我們創(chuàng)建另一個擴展Test類的類,并嘗試重寫構造函數(shù)中添加的現(xiàn)有屬性:
class ExtendedTest(Test): def __init__(self): super().__init__() self.foo = 'overridden' self._bar = 'overridden' self.__baz = 'overridden'
現(xiàn)在,你認為foo,_bar和__baz的值會出現(xiàn)在這個ExtendedTest類的實例上嗎? 我們來看一看:
>>> t2 = ExtendedTest() >>> t2.foo 'overridden' >>> t2._bar 'overridden' >>> t2.__baz AttributeError: "'ExtendedTest' object has no attribute '__baz'"
等一下,當我們嘗試查看t2 .__ baz的值時,為什么我們會得到AttributeError? 名稱修飾被再次觸發(fā)了! 事實證明,這個對象甚至沒有__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,因為它以兩個下劃線字符開頭。這表明名稱修飾不是專門與類屬性關聯(lián)的。它適用于在類上下文中使用的兩個下劃線字符開頭的任何名稱。
(2)示例
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()
執(zhí)行結果:
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中不存在真正意義上的私有變量。對于雙下劃線開頭的方法和屬性雖然我們不能直接引用,那是因為Python默認在其前面加了前綴_類名,所以就像situation 2下面的代碼,雖然我們不能用a直接訪問__method(),但卻可以加上前綴去訪問,即_A__method()。
Python的私有變量軋壓:
Python把以兩個或以上下劃線字符開頭且沒有以兩個或以上下劃線結尾的變量當作私有變量。私有變量會在代碼生成之前被轉換為長格式(變?yōu)楣校?。轉換機制是這樣的:在變量前端插入類名,再在前端加入一個下劃線字符。這就是所謂的私有變量軋壓(Private name mangling)。
注意:一是因為軋壓會使標識符變長,當超過255的時候,Python會切斷,要注意因此引起的命名沖突。
二是當類名全部以下劃線命名(如___)的時候,Python就不再執(zhí)行軋壓。
具體原因:因為類A定義了一個私有成員函數(shù)(變量),所以在代碼生成之前先執(zhí)行私有變量軋壓。軋壓之后,類A的代碼就變成這樣了:
class A(object): def _A__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._A__method() # 這行變了 self.method_x()
有點像C語言里的宏展開。因為在類B定義的時候沒有覆蓋__init__方法,所以調用的仍然是A.__init__,即執(zhí)行了self._A__method(),自然輸出“I'm a method in class A”了。
下面的兩段代碼可以增加說服力,增進理解:
class A(object): def __init__(self): self.__private() self.public() def __private(self): print 'A.__private()' def public(self): print 'A.public()' class B(A): def __private(self): print 'B.__private()' def public(self): print 'B.public()' b = B() A.__private() B.public()
原因與上述相同。類 A里的__private標識符將被轉換為_A__private,這就是_A__private和__private消失的原因了。
class C(A): def __init__(self): # 重寫 __init__ ,不再調用 self._A__private self.__private() # 這里綁定的是 _C_private self.public() def __private(self): print 'C.__private()' def public(self): print 'C.public()' c = C() C.__private() C.public()
可以看出重寫 __init__ ,不再調用 self._A__private,而是調用類C里__init__函數(shù)里綁定的_C_private。
而且在類A中也可以直接調用self._A__method(),雖然該變量未定義,但是進行命名改編時會修改。
雙前導和末尾下劃線:__var__
如果一個名字同時以雙下劃線開始和結束,則不會應用名稱修飾。 由雙下劃線前綴和后綴包圍的變量不會被Python解釋器修改:
class PrefixPostfixTest: def __init__(self): self.__bam__ = 42 >>> PrefixPostfixTest().__bam__ 42
但是,Python保留了有雙前導和雙末尾下劃線的名稱,用于特殊用途。 前后有雙下劃線表示的是特殊函數(shù)。通??梢詮蛯戇@些方法實現(xiàn)自己所需要的功能。
這樣的例子有,__init__ --- 對象構造函數(shù),__call__ --- 它使得一個對象可以被調用。表示這是Python自己調用的,程序員不要調用。比如我們可以調用len()函數(shù)來求長度,其實它后臺是調用了__len__()方法。一般我們應該使用len,而不是直接使用__len__()。
我們一般稱__len__()這種方法為magic methods(魔術方法),一些操作符后臺調用的也是也是這些magic methods,比如+后臺調用的是__add__,-調用的是__sub__,所以這種機制使得我們可以在自己的類中覆寫操作符(見后面例子)。另外,有的時候這種開頭結尾雙下劃線方法僅僅是某些特殊場景的回調函數(shù),比如__init__()會在對象的初始化時調用,__new__()會在構建一個實例的時候調用等等。
- 類內置函數(shù)和全局內置函數(shù)
- 標識符等的實現(xiàn)細節(jié)
- 回調函數(shù)
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)臨時變量
按照習慣,有時候單個獨立下劃線是用作一個名字,來表示某個變量是臨時的或無關緊要的。作為臨時性的名稱使用,但是在后面不會再次用到該名稱。這種用法在循環(huán)中會經常用到。
例如,在下面的循環(huán)中,我們不需要訪問正在運行的索引,我們可以使用“_”來表示它只是一個臨時值:
>>> for _ in range(32): ... print('Hello, World.')
你也可以在拆分(unpacking)表達式(對元組以逗號分隔開的表達式)中將單個下劃線用作“不關心的”變量,以忽略特定的值。 同樣,這個含義只是“依照約定”,并不會在Python解釋器中觸發(fā)特殊的行為。 單個下劃線僅僅是一個有效的變量名稱,會有這個用途而已。
在下面的代碼示例中,我將汽車元組拆分為單獨的變量,但我只對顏色和里程值感興趣。 但是,為了使拆分表達式成功運行,我需要將包含在元組中的所有值分配給變量。 在這種情況下,“_”作為占位符變量可以派上用場:
>>> car = ('red', 'auto', 12, 3812.4) >>> color, _, _, mileage = car >>> color 'red' >>> mileage 3812.4 >>> _ 12
(2)_代表交互式解釋器會話中上一條的執(zhí)行結果
除了用作臨時變量之外,“_”是大多數(shù)Python REPL中的一個特殊變量,它表示由解釋器執(zhí)行的最近一個表達式的結果。這種用法有點類似于Linux中的上一條命令的用法。只不過在在Python解釋器中表示的上一條執(zhí)行的結果。這種用法首先被標準CPython解釋器采用,然后其他類型的解釋器也先后采用。
這樣就很方便了,比如你可以在一個解釋器會話中訪問先前計算的結果,或者,你是在動態(tài)構建多個對象并與它們交互,無需事先給這些對象分配名字:
>>> 20 + 3 23 >>> _ 23 >>> print(_) 23 >>> list() [] >>> _.append(1) >>> _.append(2) >>> _.append(3) >>> _ [1, 2, 3]
(3)國際化
也許你也曾看到”_“會被作為一個函數(shù)來使用。這種情況下,它通常用于實現(xiàn)國際化和本地化字符串之間翻譯查找的函數(shù)名稱,這似乎源自并遵循相應的C約定。
from django.utils.translation import ugettext as _ from django.http import HttpResponse def my_view(request): output = _("Welcome to my site.") return HttpResponse(output)
場景一和場景三中的使用方法可能會相互沖突,所以我們需要避免在使用“_”作為國際化查找轉換功能的代碼塊中同時使用“_”作為臨時名稱。
到此這篇關于python 類中函數(shù)名前后加下劃線的具體使用的文章就介紹到這了,更多相關python 函數(shù)名加下劃線內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python實現(xiàn)決策樹并且使用Graphviz可視化的例子
今天小編就為大家分享一篇Python實現(xiàn)決策樹并且使用Graphviz可視化的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08python可以美化表格數(shù)據(jù)輸出結果的兩個工具
這篇文章主要介紹了python可以美化表格數(shù)據(jù)輸出結果的兩個工具,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-06-06一文搞懂Python中Pandas數(shù)據(jù)合并
pandas是基于NumPy的一種工具,該工具是為了解決數(shù)據(jù)分析任務而創(chuàng)建的。Pandas納入了大量庫和一些標準的數(shù)據(jù)模型,提供了高效操作大型數(shù)據(jù)集的工具。pandas提供大量快速便捷地處理數(shù)據(jù)的函數(shù)和方法。你很快就會發(fā)現(xiàn),它是使Python強大而高效的數(shù)據(jù)分析環(huán)境的重要因素之一2021-11-11python使用多線程查詢數(shù)據(jù)庫的實現(xiàn)示例
這篇文章主要介紹了python使用多線程查詢數(shù)據(jù)庫的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08詳細介紹在pandas中創(chuàng)建category類型數(shù)據(jù)的幾種方法
這篇文章主要介紹了詳細介紹在pandas中創(chuàng)建category類型數(shù)據(jù)的幾種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04Python實現(xiàn)的ftp服務器功能詳解【附源碼下載】
這篇文章主要介紹了Python實現(xiàn)的ftp服務器功能,結合實例形式分析了Python構建ftp服務器功能的相關設置、實現(xiàn)技巧與操作注意事項,并附帶源碼供讀者下載參考,需要的朋友可以參考下2019-06-06