透過令人驚訝的片段探索和理解 Python。
翻譯:中文 中文 |越南語 Tiếng Việt |西班牙語 | 西班牙語 |韓文 한국어 |俄語 Русский |德語 德語 |新增翻譯
其他模式:互動網站|互動筆記本
Python 是一種設計精美的基於解釋器的高級程式語言,為我們提供了許多讓程式設計師感到舒適的功能。但有時,Python 程式碼片段的結果乍看之下可能並不明顯。
這是一個有趣的項目,試圖解釋 Python 中一些反直覺的片段和鮮為人知的功能的幕後到底發生了什麼。
雖然您在下面看到的一些範例可能不是真正意義上的 WTF,但它們將揭示您可能不知道的 Python 的一些有趣部分。我發現這是學習程式語言內部原理的好方法,我相信您也會發現它很有趣!
如果您是一位經驗豐富的 Python 程式設計師,那麼您可以將在第一次嘗試中就完成大部分任務視為一項挑戰。你可能已經經歷過其中一些,而我也許能夠喚起你甜蜜的舊回憶! ?
PS:如果您是老讀者,您可以在這裡了解新的修改(標有星號的範例是最新主要修訂中新增的範例)。
那麼,我們開始吧...
is
不使用方法is not ...
不是is (not ...)
del
操作goto
,但為什麼呢?+=
更快dict
查找速度*dict
*所有範例的結構如下:
▶ 一些奇特的標題
# Set up the code. # Preparation for the magic...輸出(Python 版本):
> >> triggering_statement Some unexpected output(可選):一行描述意外輸出。
解釋:
- 簡要解釋正在發生的事情以及為什麼會發生。
# Set up code # More examples for further clarification (if necessary)輸出(Python 版本):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
注意:所有範例均在 Python 3.5.2 互動式解釋器上進行測試,除非在輸出之前明確指定,否則它們應適用於所有 Python 版本。
在我看來,充分利用這些範例的一個好方法是按順序閱讀它們,並且對於每個範例:
由於某種原因,Python 3.8 的「海象」運算子 ( :=
) 變得相當流行。讓我們檢查一下,
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2.
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
海像操作員快速複習
海象運算子 ( :=
) 是在 Python 3.8 中引入的,它在您想要為表達式中的變數賦值的情況下非常有用。
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
輸出(> 3.8):
5
5
5
這節省了一行程式碼,並隱式阻止了呼叫some_func
兩次。
未加括號的「賦值表達式」(使用 walrus 運算子)在頂層受到限制,因此第一個片段的a := "wtf_walrus"
語句中出現SyntaxError
。加上括號後它按預期工作並分配了a
.
與往常一樣,不允許將包含=
運算子的表達式加括號。因此(a, b = 6, 9)
中存在語法錯誤。
Walrus 運算符的語法格式為NAME:= expr
,其中NAME
是有效標識符, expr
是有效表達式。因此,不支援可迭代的打包和解包,這意味著,
(a := 6, 9)
等價於((a := 6), 9)
並最終等價於(a, 9)
(其中a
的值為 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
類似地, (a, b := 16, 19)
等價於(a, (b := 16), 19)
它只不過是一個 3 元組。
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
輸出(<Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
有道理,對吧?
'wtf'
將被實習,但''.join(['w', 't', 'f'])
不會被實習)'wtf!'
沒有被實習是因為!
。此規則的 CPython 實作可以在這裡找到a
和b
設定為"wtf!"
時在同一行中,Python 解譯器會建立一個新對象,然後同時引用第二個變數。如果你在不同的行上執行此操作,它不會“知道”已經有"wtf!"
作為一個物件(因為"wtf!"
並沒有隱式地依照上面提到的事實保留)。這是編譯時最佳化。此最佳化不適用於 3.7.x 版本的 CPython(請參閱此問題以獲取更多討論)。a, b = "wtf!", "wtf!"
是單一語句,而a = "wtf!"; b = "wtf!"
是一行中的兩個語句。這解釋了為什麼a = "wtf!"; b = "wtf!"
,並解釋為什麼它們在some_file.py
中調用時相同'a'*20
在編譯期間被替換為'aaaaaaaaaaaaaaaaaaaa'
以在運行時節省一些時脈週期。常數折疊僅發生.pyc
長度'a'*10**10
21 的字串中。這是相同的實作來源。 > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
根據 https://docs.python.org/3/reference/expressions.html#comparisons
形式上,若a, b, c, ..., y, z 是表達式,op1, op2, ..., opN 是比較運算符,則a op1 b op2 c ... y opN z 等價於a op1 b b op2 c 和 ... y opN z,不同之處在於每個表達式最多計算一次。
雖然在上面的範例中,這種行為對您來說可能看起來很愚蠢,但對於a == b == c
和0 <= x <= 100
之類的東西來說,這是非常棒的。
False is False is False
相當於(False is False) and (False is False)
True is False == False
相當於(True is False) and (False == False)
並且由於語句的第一部分 ( True is False
) 計算結果為False
,因此整個表達式計算結果為False
。1 > 0 < 1
相當於(1 > 0) and (0 < 1)
其計算結果為True
。(1 > 0) < 1
等價於True < 1
並且 > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
計算結果為False
is
不使用方法以下是網路上出現的一個非常著名的例子。
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3、輸出
> >> a , b = 257 , 257
> >> a is b
True
輸出(特別是 Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
is
和==
的區別
is
運算子檢查兩個運算元是否引用同一個物件(即,它檢查運算元的標識是否符合)。==
運算子比較兩個運算元的值並檢查它們是否相同。is
代表引用相等, ==
代表值相等。一個例子來澄清事情, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
是現有對象,但257
不是
當你啟動 python 時,將分配從-5
到256
數字。這些數字被經常使用,因此準備好它們是有意義的。
引用自https://docs.python.org/3/c-api/long.html
目前的實作為 -5 到 256 之間的所有整數保留一個整數物件數組,當您建立該範圍內的 int 時,您只需傳回對現有物件的參考。所以應該可以改變 1 的值。 :-)
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
這裡,解釋器在執行y = 257
時不夠聰明,無法識別我們已經創建了值257,
因此它繼續在記憶體中創建另一個物件。
類似的最佳化也適用於其他不可變對象,例如空元組。由於列表是可變的,這就是為什麼[] is []
將返回False
而() is ()
將返回True
。這解釋了我們的第二個片段。我們繼續第三個,
當在同一行中使用相同的值初始化時, a
和b
都引用同一個物件。
輸出
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
當同一行中的 a 和 b 設定為257
時,Python 解譯器會建立一個新對象,然後同時引用第二個變數。如果您在單獨的行上執行此操作,它不會「知道」已經有257
作為物件。
它是一種編譯器最佳化,特別適用於互動式環境。當您在即時解釋器中輸入兩行時,它們會單獨編譯,因此會單獨最佳化。如果您要在.py
檔案中嘗試此範例,您將不會看到相同的行為,因為該檔案是一次性編譯的。這種最佳化不僅限於整數,它也適用於其他不可變資料類型,例如字串(檢查“字串是棘手的範例”)和浮點數,
> >> a , b = 257.0 , 257.0
> >> a is b
True
為什麼這不適用於 Python 3.7?抽象原因是因為此類編譯器最佳化是特定於實現的(即可能隨版本、作業系統等而變化)。我仍在弄清楚到底是什麼實施變更導致了該問題,您可以查看此問題以獲取更新。
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
輸出:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
那麼,為什麼 Python 無所不在呢?
Python 字典中鍵的唯一性是透過等價性,而不是同一性。因此,即使5
、 5.0
和5 + 0j
是不同類型的不同對象,但由於它們相等,因此它們不能同時位於同一個dict
(或set
)中。一旦插入其中任何一個,嘗試尋找任何不同但等效的鍵將成功並使用原始映射值(而不是因KeyError
失敗):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
這在設定項目時也適用。因此,當您執行some_dict[5] = "Python"
時,Python 會找到具有等效鍵5.0 -> "Ruby"
的現有項目,覆蓋其值,並保留原始鍵。
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
那我們要如何將金鑰更新為5
(而不是5.0
)?我們實際上無法就地進行此更新,但我們可以做的是先刪除鍵( del some_dict[5.0]
),然後設定它( some_dict[5]
)以獲取整數5
作為鍵而不是浮動5.0
,儘管在極少數情況下應該需要它。
Python 如何在包含5.0
的字典中找到5
? Python 在恆定時間內完成此操作,而無需使用雜湊函數掃描每個項目。當 Python 在字典中尋找鍵foo
時,它首先計算hash(foo)
(以恆定時間運行)。由於在 Python 中,要求比較相等的物件也具有相同的雜湊值(此處的文件),因此5
、 5.0
和5 + 0j
具有相同的雜湊值。
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
注意:反之亦然:具有相等雜湊值的物件本身可能不相等。 (這會導致所謂的哈希衝突,並降低哈希通常提供的恆定時間性能。)
class WTF :
pass
輸出:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
當id
被呼叫時,Python 創建了一個WTF
類別物件並將其傳遞給id
函數。 id
函數取得其id
(其記憶體位置),並丟棄該物件。物體被破壞。
當我們連續兩次執行此操作時,Python 也會為第二個物件分配相同的記憶體位置。由於(在CPython中) id
使用記憶體位置作為物件id,因此兩個物件的id是相同的。
因此,物件的 id 僅在物件的生命週期內是唯一的。在物件被銷毀之後或創建之前,其他物件可以具有相同的 id。
但為什麼is
運算子的計算結果為False
?讓我們看看這個片段。
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
輸出:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
正如您可能觀察到的,物件被銷毀的順序是造成這裡所有差異的原因。
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
輸出
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
這是怎麼回事?
不及物相等在dictionary
、 ordered_dict
和another_ordered_dict
之間不成立的原因是因為OrderedDict
類別中實作__eq__
方法的方式。來自文件
OrderedDict 物件之間的相等測試是順序敏感的,並實作為
list(od1.items())==list(od2.items())
。OrderedDict
物件和其他 Mapping 物件之間的相等性測試與常規字典一樣不區分順序。
這種行為相等的原因是它允許在使用常規字典的任何地方直接替換OrderedDict
物件。
好的,那麼為什麼改變順序會影響生成的set
物件的長度呢?答案只是缺乏不及物平等。由於集合是唯一元素的「無序」集合,因此插入元素的順序並不重要。但在這種情況下,這確實很重要。讓我們稍微分解一下,
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
因此,不一致是由於another_ordered_dict in another_set
為False
,因為ordered_dict
已經存在於another_set
中,並且如之前所觀察到的, ordered_dict == another_ordered_dict
為False
。
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
輸出:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
try
套件中執行return
、 break
或continue
語句時, finally
子句也會在退出時執行。return
語句決定。由於finally
子句總是執行,因此finally
子句中執行的return
語句將始終是最後執行的語句。return
或break
語句,則暫時儲存的例外會被丟棄。 some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
輸出:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
for
語句在 Python 語法中定義為:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
其中exprlist
是賦值目標。這意味著對可迭代中的每個項目執行相當於{exprlist} = {next_value}
的操作。一個有趣的例子可以說明這一點:
for i in range ( 4 ):
print ( i )
i = 10
輸出:
0
1
2
3
您是否期望循環只運行一次?
解釋:
i = 10
永遠不會影響迴圈的迭代。在每次迭代開始之前,迭代器提供的下一個項目(在本例中range(4)
)被解包並分配給目標列表變數(在本例中為i
)。 enumerate(some_string)
函數在每次迭代中產生一個新值i
(一個遞增的計數器)和some_string
中的一個字元。然後它將字典some_dict
的(剛剛分配的) i
鍵設定為該字元。循環的展開可以簡化為:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
輸出:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
輸出:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
輸出:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
在生成器表達式中, in
子句在聲明時計算,但條件子句在執行時計算。
因此在運行時之前, array
被重新分配給列表[2, 8, 22]
,並且由於在1
, 8
和15
中,只有8
的計數大於0
,因此生成器僅產生8
。
第二部分中g1
和g2
的輸出差異是由於變數array_1
和array_2
重新賦值的方式所造成的。
在第一種情況下, array_1
綁定到新物件[1,2,3,4,5]
,並且由於in
子句在宣告時求值,因此它仍然引用舊物件[1,2,3,4]
(沒有被破壞)。
在第二種情況下,對array_2
切片分配將相同的舊物件[1,2,3,4]
更新為[1,2,3,4,5]
。因此g2
和array_2
仍然引用同一個物件(現已更新為[1,2,3,4,5]
)。
好吧,按照到目前為止討論的邏輯,第三個片段中list(gen)
的值不應該是[11, 21, 31, 12, 22, 32, 13, 23, 33]
嗎? (因為array_3
和array_4
的行為就像array_1
)。 PEP-289 中解釋了(僅) array_4
值更新的原因
只有最外面的 for 表達式會立即計算,其他表達式會延遲到生成器運行為止。
is not ...
不是is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
是單一二元運算符,其行為與使用is
和not
分隔不同。is not
計算為False
,否則計算為True
。(not None)
計算結果為True
因為值None
在布林上下文中為False
,因此表達式變為'something' is True
。 # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
輸出:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
我們沒有分配三個"X"
,不是嗎?
當我們初始化row
變數時,該視覺化解釋了記憶體中發生的情況
當透過乘以row
來初始化board
時,這就是記憶體內部發生的情況(每個元素board[0]
、 board[1]
和board[2]
都是對row
引用的相同列表的引用)
我們可以透過不使用row
變數來產生board
來避免這種情況。 (在本期中詢問)。
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
輸出(Python版本):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
在將some_func
附加到funcs
之前,每次迭代中x
的值都不同,但所有函數在循環完成後求值時都會回傳 6。
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
,而不是使用建立函數時的x
值。因此所有函數都使用分配給變數的最新值進行計算。我們可以看到它使用周圍上下文中的x
(即不是局部變數): > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
由於x
是全域值,我們可以透過更新x
來更改funcs
將查找和傳回的值:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
的值。 funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
輸出:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
它不再在全域範圍內使用x
:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
那麼哪個是「最終」基底類別呢?順便說一句,還有更多令人困惑的事情,
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
是Python 中的元類別。object
,其中包括類別及其對象(實例)。type
是類別object
的元類,每個類別(包括type
)都直接或間接繼承自object
。object
和type
之間沒有真正的基底類別。上面程式碼片段中的混亂之所以出現,是因為我們正在根據 Python 類別來考慮這些關係( issubclass
和isinstance
)。 object
和type
之間的關係無法在純Python中重現。更準確地說,以下關係無法在純 Python 中重現,object
和type
之間的這些關係(兩者都是彼此的實例,也是它們自己的實例),是因為實作層級上的「作弊」。輸出:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
子類關係應該是可傳遞的,對吧? (即,如果A
是B
的子類,且B
是C
的子類,則A
應該是C
的子類)
__subclasscheck__
。issubclass(cls, Hashable)
時,它只是在cls
或其繼承的任何內容中尋找非 Falsey“ __hash__
”方法。object
是可散列的,但list
是不可散列的,因此它破壞了傳遞性關係。 class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
輸出:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
訪問classm
兩次,我們得到一個相等的對象,但不是同一個?讓我們看看SomeClass
的實例會發生什麼:
o1 = SomeClass ()
o2 = SomeClass ()
輸出:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
訪問classm
或method
兩次,會為SomeClass
的相同實例建立相等但不相同的物件。
self
作為第一個參數,儘管沒有明確傳遞它)。 > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
永遠不會為真。然而,將函數作為類別屬性(而不是實例)存取並不創建方法;而是創建方法。所以SomeClass.method is SomeClass.method
是真的。 > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
將函數轉換為類別方法。類別方法是描述符,在存取時,它會建立一個綁定物件的類別(類型)而不是物件本身的方法物件。 > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
在作為類別屬性存取時也會建立一個方法(在這種情況下,它們綁定類,而不是它的類型)。所以SomeClass.classm is SomeClass.classm
是假的。 > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
是真的,儘管記憶體中不是同一個物件。staticmethod
將函數轉換為“無操作”描述符,它按原樣返回函數。沒有建立任何方法對象,因此與is
進行比較是正確的。 > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
影響效能。 CPython 3.7 透過引入新的操作碼來解決這個問題,這些操作碼可以在不建立臨時方法物件的情況下處理呼叫方法。這僅在實際調用所訪問的函數時使用,因此這裡的程式碼片段不受影響,並且仍然生成方法:) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
為什麼會出現這種 True-False 變化?
all
函數的實作等價於
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
傳回True
因為可迭代物件為空。
all([[]])
傳回False
因為傳遞的陣列只有一個元素[]
,而在 python 中,空列表是假的。
all([[[]]])
和更高的遞歸變體總是為True
。這是因為傳遞的陣列的單一元素 ( [[...]]
) 不再為空,並且包含值的列表是真實的。
輸出(< 3.6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
輸出:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
所示),反斜線按原樣傳遞自身以及轉義後續字元的行為。 > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
),反斜線轉義了尾部引號,使解析器沒有終止引號(因此出現SyntaxError
)。這就是為什麼反斜線在原始字串末尾不起作用的原因。 x = True
y = False
輸出:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
運算子的優先權高於 Python 中的not
運算子。not x == y
等價於not (x == y)
等價於not (True == False)
最終評估為True
。x == not y
會引發SyntaxError
,因為它可以被認為等同於(x == not) y
和 not x == (not y)
這可能是您乍一看所期望的。not
標記成為not in
運算子的一部分(因為==
和not in
運算子具有相同的優先權),但在無法找到not
標記後面的in
標記後,它會引發SyntaxError
。輸出:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
和"""
也是 Python 中的字串分隔符,這會導致語法錯誤,因為 Python 解釋器在掃描當前遇到的三引號字串文字時期望終止三引號作為分隔符。1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
輸出:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
輸出(< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
是 Python 中int
的子類
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
因此, True
和False
是int
的實例
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
True
的整數值為1
, False
的整數值為0
。
> >> int ( True )
1
> >> int ( False )
0
請參閱此 StackOverflow 答案以了解背後的基本原理。
最初,Python 沒有bool
類型(人們使用 0 表示 false,使用非零值(如 1 表示 true))。 True
、 False
和bool
類型在 2.x 版本中添加,但是,為了向後相容, True
和False
不能設為常數。它們只是內建變量,可以重新分配它們
Python 3 向後不相容,問題最終得到解決,因此最後一個程式碼片段無法與 Python 3.x 一起使用!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
輸出:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
輸出:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
運算子就地修改可變對象,而不建立新對象。因此,更改一個實例的屬性也會影響其他實例和類別屬性。 some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
輸出(<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
的錯誤。yield
並會拋出SyntaxError
。1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
輸出(> 3.3):
> >> list ( some_func ( 3 ))
[]
"wtf"
去哪了?是由於yield from
的一些特殊影響嗎?讓我們驗證一下,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
輸出:
> >> list ( some_func ( 3 ))
[]
結果一樣,這也不行。
return
語句(請參閱 PEP380)。官方文件說,“...生成器中的
return expr
會導致在退出生成器時引發StopIteration(expr)
。”
在some_func(3)
的情況下,由於return
語句, StopIteration
在開始時被引發。 StopIteration
異常會在list(...)
包裝器和for
循環內自動捕獲。因此,上述兩個片段會產生一個空列表。
要從生成器some_func
取得["wtf"]
我們需要捕獲StopIteration
異常,
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
輸出:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
和'nan'
是特殊字串(不區分大小寫),當明確轉換為float
類型時,它們分別用於表示數學“無窮大”和“非數字”。
由於根據 IEEE 標準NaN != NaN
,遵守此規則打破了 Python 中集合元素的自反性假設,即如果x
是像list
這樣的集合的一部分,則比較等實現基於x == x
的假設。由於這種假設,在比較兩個元素時首先比較身份(因為它更快),並且僅當身份不匹配時才比較值。下面的程式碼片段將使事情變得更清楚,
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
由於x
和y
的身份不同,所以考慮的值也不同;因此這次比較回傳False
。
有趣的讀物:反身性和文明的其他支柱
如果您知道引用在 Python 中的工作原理,這可能看起來微不足道。
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
輸出:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
但我認為元組是不可變的...
引自https://docs.python.org/3/reference/datamodel.html
不可變序列 不可變序列類型的物件一旦建立就不能更改。 (如果該物件包含對其他物件的引用,則這些其他物件可能是可變的並且可以被修改;但是,由不可變物件直接引用的物件集合不能更改。)
+=
運算子就地更改清單。項目分配不起作用,但當異常發生時,該項目已就地更改。
Python 官方常見問題中也有解釋。
e = 7
try :
raise Exception ()
except Exception as e :
pass
輸出(Python 2.x):
> >> print ( e )
# prints nothing
輸出(Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
資料來源:https://docs.python.org/3/reference/compound_stmts.html# except
當使用as
目標分配異常時,它會在except
子句的末尾被清除。這就像
except E as N :
foo
被翻譯成
except E as N :
try :
foo
finally :
del N
這意味著必須為異常分配一個不同的名稱,以便能夠在 except 子句之後引用它。異常會被清除,因為附加了回溯,它們與堆疊幀形成了一個引用循環,使該幀中的所有局部變數保持活動狀態,直到發生下一次垃圾收集。
這些子句不在 Python 中。範例中的所有內容都存在於相同範圍內,並且由於執行except
子句,因此變數e
被刪除。對於具有獨立內部作用域的函數來說,情況並非如此。下面的例子說明了這一點:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
輸出:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
在 Python 2.x 中,變數名稱e
被指派給Exception()
實例,因此當您嘗試列印時,它不會列印任何內容。
輸出(Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
輸出:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
物件s
和字串"s"
雜湊都達到相同的值,因為SomeClass
繼承了str
類別的__hash__
。
SomeClass("s") == "s"
評估為True
,因為SomeClass
也從str
類別中繼承了__eq__
方法。
由於兩個物件都達到相同的值並且相等,因此它們在字典中以相同的鍵表示。
對於所需的行為,我們可以在SomeClass
中重新定義__eq__
方法
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
輸出:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
輸出:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
賦值語句計算表達式列表(請記住,這可以是單一表達式或逗號分隔的列表,後者產生一個元組),並將單一結果物件從左到右分配給每個目標列表。
+
in (target_list "=")+
意味著可以有一個或多個目標清單。在這種情況下,目標列表為a, b
和a[b]
(請注意,表達式列表正是一個,在我們的情況下是{}, 5
)。
評估表達式清單後,其值將從左至右開啟目標清單。因此,在我們的情況下,首先將{}, 5
元組解開為a, b
,現在我們有a = {}
和b = 5
。
a
現在被指派給{}
,這是一個可變的物件。
第二個目標清單是a[b]
(您可能希望這會引發錯誤,因為a
和b
但請記住,我們只是分配了a
至{}
和b
到5
)。
現在,我們將字典中的金鑰5
設定為元組({}, 5)
建立圓形參考(輸出中的{...}
是指a
已經引用的相同物件)。循環參考的另一個更簡單的例子可能是
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
在我們的例子中是類似的( a[b][0]
與a
相同的物件)
因此總而言之,您可以將範例分解為
a , b = {}, 5
a [ b ] = a , b
循環引用可以透過a[b][0]
事實證明是合理的a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
輸出:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
python 3.10.6的呼叫int()
運作良好,並在Python 3.10.8中提高了一個值。請注意,Python仍然可以與大整數一起使用。僅在整數和字串之間轉換時會增加誤差。
幸運的是,當您期望操作超過該操作時,您可以增加允許數量的限制。為此,您可以使用以下一個:
如果您希望程式碼超過此值,請檢查文件以取得更改預設限制的更多詳細資訊。
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
輸出(Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
是的,它運行八次並停止。
RuntimeError: dictionary keys changed during iteration
。del
操作 class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
輸出: 1。
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
PHEW,最後刪除。您可能已經猜到了是什麼節省了__del__
在我們第一次刪除x
的嘗試中被稱為。讓我們在範例中添加更多曲折。
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
好吧,現在被刪除了嗎?
del x
不會直接呼叫x.__del__()
。del x
時,python從目前範圍中刪除名稱x
,並減少1個引用物件x
的參考計數。 __del__()
僅在物件的參考計數達到零時才呼叫。__del__()
,因為互動式解釋器中的先前語句( >>> y
)創建了對同一物件的另一個引用(具體來說, _
魔術變量,該變量引用了最後一個non nonno None
的結果值在臥式上的表達),因此在遇到del y
時阻止參考計數達到零。globals
(或實際執行任何將具有None
結果的任何內容)導致_
引用新結果,從而刪除現有參考。現在,參考計數達到0,我們可以看到“已刪除!”被印(終於!)。1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
輸出:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
當您對範圍中變數進行分配時,它將成為該範圍的本地化。因此, a
成為another_func
的範圍的本地,但是先前尚未在同一範圍中初始化它,從而引發錯誤。
要在another_func
中修改外部範圍變數a
,我們必須使用global
關鍵字。
def another_func ()
global a
a += 1
return a
輸出:
> >> another_func ()
2
在another_closure_func
中, a
成為another_inner_func
範圍的本地,但是先前尚未以同一範圍初始化它,這就是為什麼會引發錯誤的原因。
若要修改another_inner_func
中的外部範圍變數a
,請使用nonlocal
關鍵字。非本地語句用於指在最近的外部(不包括全域)範圍中定義的變數。
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
輸出:
> >> another_func ()
2
關鍵字global
和nonlocal
告訴Python解釋器不聲明新變量,並在相應的外部範圍中找到它們。
閱讀此簡短但很棒的指南,以了解有關名稱空間和範圍分辨率在Python中的工作方式的更多資訊。
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
輸出:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
您能猜出為什麼輸出是[2, 4]
?
更改要迭代的物件絕不是一個好主意。這樣做的正確方法是迭代物件的副本, list_3[:]
就是這樣做的。
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
del
, remove
和pop
的差異:
del var_name
只是從本地或全域名稱空間中刪除了var_name
的綁定(這就是為什麼list_1
不受影響的原因)。remove
刪除第一個符合值,而不是特定索引,如果找不到該值,則會提高ValueError
。pop
在特定的索引處刪除元素並將其傳回,如果指定了無效的索引,則將IndexError
。為什麼輸出是[2, 4]
?
list_2
或list_4
刪除1
索引時,列表的內容現在為[2, 3, 4]
。其餘元素被向下移動,即索引2
2
3
在索引1處3
列表序列中的每個替代元素都會發生類似的事情。 > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
元素3
從何處從numbers
列表中取得?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
列表中,透過呼叫下next
函數,並在任何峰值耗盡時停止。result
清單中的現有元素被丟棄。這就是numbers_iter
中3
的情況。zip
的正確方法是, > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
輸出:
6 : for x inside loop
6 : x in global
但是x
從未被定義在循環範圍之外...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
輸出:
6 : for x inside loop
6 : x in global
3.
輸出(Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
輸出(Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
在Python,For-Loops使用它們存在的範圍,並將其定義的環變數拋在後面。如果我們以前在全域名稱空間中明確定義了循環變量,這也適用。在這種情況下,它將重新啟動現有變數。
Python 2.x和Python 3.x解釋器的列表理解範例的輸出的差異可以透過以下記錄在Python 3.0 ChangElog中的新事物的變更來解釋:
「列表綜合不再支援句法形式
[... for var in item1, item2, ...]
。使用[... for var in (item1, item2, ...)]
。另外,請注意該列表綜合語義具有不同的語義:對於list()
構造函數中的發電機表達式,它們更接近句法糖,尤其是環路控制變數不再洩漏到周圍的範圍中。
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
輸出:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
每次您呼叫函數時,Python中功能的預設可變參數並不是真正初始化的。相反,最近分配給它們的值用作預設值。當我們將[]
明確傳遞給some_func
作為參數時,未使用default_arg
變數的預設值,因此該函數會按預期傳回。
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
輸出:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
避免因可變參數而避免錯誤的常見做法是將None
分配為預設值,然後檢查是否將任何值傳遞給與該參數相對應的函數。例子:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
輸出(Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
輸出(Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
要將多個異常添加到除外,您需要將它們作為括號元素作為第一個參數傳遞。第二個參數是一個可選名稱,在提供後將綁定已提出的異常實例。例子,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
輸出(Python 2.x):
Caught again!
list.remove(x): x not in list
輸出(Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
將異常與變數與逗號分開是棄用的,並且在Python 3中不起作用;正確的方法是使用as
例子,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
輸出:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
輸出:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
輸出:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
行為並不總是與a = a + b
相同的方式。類別可能以不同的方式實現op=
運算符,並且清單執行此操作。
表達式a = a + [5,6,7,8]
產生了一個新列表,並設定了對該新列表的a
,而b
卻沒有變化。
表達式a += [5,6,7,8]
實際上映射到在列表上運行的「擴展」函數,以使a
和b
仍指向已修改的相同列表。
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
輸出:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
輸出(Python 2.x):
> >> SomeClass . y [ 0 ]
17
輸出(Python 3.x):
> >> SomeClass . y [ 0 ]
5
讓我們實作一個幼稚的函數以獲取列表的中間元素:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
似乎Python舍入2.5至2。
round()
使用銀行家的圓形,其中.5個分數被舍入到最接近的數字: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
僅傳回1,因為索引為round(0.5) - 1 = 0 - 1 = -1
,傳回清單中的最後一個元素。到目前為止
1.
x , y = ( 0 , 1 ) if True else None , None
輸出:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
輸出:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
輸出
> >> len ( ten_words_list )
9
4。
a = "python"
b = "javascript"
輸出:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
輸出:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
輸出:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
對於1, x, y = (0, 1) if True else (None, None)
。
對於2,對預期行為的正確說明是t = ('one',)
,或t = 'one',
(缺少逗號),否則解釋器認為t
是str
,並按字元迭代。
()
是一個特殊的令牌,表示空tuple
。
在3中,正如您可能已經弄清楚的那樣,列表中的第五元素( "that"
)之後有一個缺失的逗號。因此,透過隱式字串字面串聯,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
我們沒有斷言單一表達式a == b
,而是在第四個片段中提出了任何AssertionError
。以下片段將清除一切,
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
至於第五段, dict.update
修改序列/ None
物件的項目(如list.append
, list.sort
等)等。如果可以在此處完成操作(從此處引用),則背後的理由是透過避免製作物件的副本來提高效能。
最後一個應該是相當明顯的,可以在函數中更改可變的物件(喜歡list
),而不成熟的( a -= 1
)的重新分配不是值的改變。
從長遠來看,了解這些挑剔可以節省您數小時的調試工作。
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
,但根據文檔如果 sep 未指定或為
None
,則應用不同的分割演算法:連續的空格被視為單一分隔符,並且如果字串具有前導或尾隨空格,則結果將在開頭或結尾不包含空字串。因此,使用 None 分隔符號分割空字串或僅包含空格的字串將傳回[]
。如果給出 sep,則連續的分隔符號不會組合在一起,並被視為分隔空字串(例如,'1,,2'.split(',')
返回['1', '', '2']
)。使用指定分隔符號拆分空字串會回傳['']
。
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
輸出
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
通常建議不要使用通配符進口。造成這種情況的第一個明顯原因是,在通配符進口中,帶有領先的下劃線的名稱不會被進口。這可能會導致運行時錯誤。
如果我們from ... import a, b, c
語法中使用,則不會發生上述NameError
。
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
如果您真的想使用通配符導入,則必須在模組中定義列表__all__
,其中包含一個公共物件列表,當我們進行通配符導入時將可用。
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
輸出
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
sorted
方法始終傳回列表,比較列表和元素總是在python中傳回False
。
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
與sorted
不同, reversed
方法傳回迭代器。為什麼?因為排序需要迭代器進行就位或使用額外的容器(列表)進行修改,而逆轉可以簡單地從最後一個索引到第一個索引來工作。
因此,在比較sorted(y) == sorted(y)
期間,第一個sorted()
將消耗迭代器y
,而下一個呼叫只會傳回一個空列表。
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
輸出(<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
午夜未列印。
False
Python 3.5之前, datetime.time
的布林值。使用if obj:
語法檢查obj
是null還是等效的「空」時,它容易出錯。
本節包含一些關於Python的鮮為人知和有趣的事情,這些事情大多數像我這樣的初學者都沒有意識到(嗯,不再了)。
好吧,給你
import antigravity
輸出: SSHH ...這是一個超級秘密。
antigravity
模組是Python開發人員發布的少數復活節彩蛋之一。import antigravity
開啟了一個網頁瀏覽器,指向有關Python的經典XKCD漫畫。goto
,但是為什麼呢? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
輸出(Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
的工作版本被宣佈為April愚人的笑話。goto
的原因。如果您是不喜歡在Python中使用Whitespace表示範圍的人之一,則可以透過匯入來使用C風格{}
from __future__ import braces
輸出:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
牙套?決不!如果您認為這很令人失望,請使用Java。好的,另一個令人驚訝的事情,您能找到__future__
模組程式碼中提出的SyntaxError
在哪裡?
__future__
模組通常用於提供未來版本的Python的功能。然而,在這種特定情況下的「未來」具有諷刺意味。future.c
檔案中存在的。future.c
運行適當的程式碼。輸出(Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
我們開始吧。
這與2009年4月1日發布的PEP-401有關(現在您知道這是什麼意思)。
引用PEP-401
認識到!
巴里叔叔不得不分享更多的事情。您可以在這裡閱讀它們。
它在互動式環境中運作良好,但是當您透過Python檔案運行時,它將提高SyntaxError
(請參閱此問題)。但是,您可以將語句包裝在eval
或compile
中以使其正常工作,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
等等,這是什麼? this
是愛❤️
輸出:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
這是Python的禪宗!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
模組是Python禪宗的復活節彩蛋(PEP 20)。love is not True or False; love is love
,諷刺的是,這是不言自明的(如果沒有的話,請參見與is
和is not
操作員有關的示例)。循環的else
子句。一個典型的例子可能是:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
輸出:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
異常處理中的else
條款。一個例子,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
輸出:
Try block executed successfully ...
break
時,才執行循環後的else
子句。您可以將其視為“ Nobreak”條款。else
子句也稱為“完成子句”,因為在try
語句中達到else
子句意味著嘗試區塊實際上成功完成。 def some_func ():
Ellipsis
輸出
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
是一個全球可用的內建對象,相當於...
> >> ...
Ellipsis
pass
語句一樣) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
是一系列陣列。假設我們要列印所有最內部陣列的第二個元素(索引1
),我們可以使用省略號來繞過所有前面的維度 > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
或Tuple[str, ...]
))))拼字是意圖的。請不要為此提交補丁。
輸出(Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
的哈希是“-10⁵xπ”,而python 2中的“-10⁵xe”。1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
輸出:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
輸出:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
為什麼Yo()._Yo__honey
工作?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
輸出:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(雙重下劃線又稱為「 Dunder」)開頭,而不是在前面加上_NameOfTheClass
來結束多個尾隨下劃線。__honey
屬性,我們必須將_Yo
附加到前面,這將防止與其他任何類別中定義的同名屬性發生衝突。return __variable
中的名稱__variable
___variable被弄_A__variable
,這也恰好是我們在外部範圍中宣告的變數的名稱。輸出:
> >> value = 11
> >> valuе = 32
> >> value
11
呃?
注意:重現這一點的最簡單方法是簡單地複製上述片段的語句並將其貼上到您的文件/外殼中。
有些非西方字元看起來與英語字母中的字母相同,但被解釋器視為不同。
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
內建的ord()
函數傳回字元的Unicode程式碼點,以及西里爾「 e」和Latin'E'的不同程式碼位置證明了上述範例的行為。
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
輸出:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
諾貝爾獎在哪裡?
energy_send
函數中建立的NUMPY數組未返回,因此記憶體空間可以自由重新分配。numpy.empty()
返回下一個免費記憶體插槽,而無需重新定位它。這個記憶點恰好是剛被釋放(通常但並非總是如此)相同的。 def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
輸出(Python 2.x):
> >> square ( 10 )
10
那不是100嗎?
注意:如果您無法重現此功能,請嘗試透過Shell執行檔案Mixed_tabs_and_spaces.py。
不要混合選項卡和空間!剛剛傳回的字元是“選項卡”,程式碼在範例其他地方的“ 4個空格”中縮排。
這就是Python處理選項卡的方式:
首先,標籤被(從左到右)替換1到八個空間,以至於替換的字元總數為八個<...>的倍數。
因此, square
功能的最後一行的“選項卡”被八個空間替換為循環。
Python 3夠友善,可以自動拋出錯誤。
輸出(Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
更快 # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
對於串聯超過兩個字串的速度比+
快,因為在計算完整的字串時不會破壞第一個字串(範例, s1
s1 += s2 + s3
)。 def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
輸出:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
讓我們將迭代次數增加10倍。
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
您可以在這些連結上閱讀有關TimeIt或%TimeIt的更多資訊。它們用於測量代碼件的執行時間。
不要使用+
來產生長字串 - 在python中, str
是不變的,因此必須將左右字串複製到每對串聯的新字串中。如果您的長度為10的四個字串,則您將複製(10+10)+((10+10)+10)+((((10+10)+10)+10)= 90個字符,而不僅僅是40個字符人物。隨著字串的數字和大小的增加,情況會變得更糟(與add_bytes_with_plus
函數的執行時間合理)
因此,建議使用.format.
或%
語法(但是,對於非常短的字串,它們比+
略慢)。
或者更好的是,如果您已經有內容的內容,則可以使用''.join(iterable_object)
,這要快得多。
與add_bytes_with_plus
不同,由於上一個範例中討論的+=
最佳化, add_string_with_plus
並未顯示執行時間的二次增加。如果語句為s = s + "x" + "y" + "z"
而不是s += "xyz"
,則增加將是二次的。
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
與Python的禪宗形成鮮明對比的是,要格式化和創建巨型字串的多種方法,據此
應該有一種——最好只有一種——明顯的方法來做到這一點。
dict
查找 * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
輸出:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
為什麼相同的查找變慢?
str
, int
,任何物件...),而專門的鍵則適用於由str
的鍵組成的常見字典。lookdict_unicode
)知道所有現有的鍵(包括查找鍵)都是字串,並且使用更快,更簡單的字串比較來比較鍵,而不是呼叫__eq__
方法。str
鍵存取dict
實例時,它進行了修改,因此未來的查找使用通用函數。dict
實例,此過程不可逆轉,且鍵甚至不必存在。這就是為什麼嘗試失敗查找具有相同效果的原因。dict
S * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
輸出:( Python 3.8,其他3個版本可能會有所不同)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
讓我們再試一次...在新的解釋器中:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
是什麼使這些字典變成腫?為什麼新創建的物件也腫了?
__init__
中新增許多屬性,而不會導致添加許多屬性“不共樣”)。如果在調整大小時存在多個實例,則在同一類別的所有未來實例中停用金鑰共用:cpython無法確定您的實例是否正在使用相同的屬性集,並決定在嘗試共用他們的實例時紓困鍵。__init__
中的所有屬性! join()
是字串操作而不是清單操作。 (先使用式有點違反直覺)
說明:如果join()
是字串上的一種方法,則可以在任何迭代(列表,元組,迭代器)上操作。如果是清單上的一種方法,則必須由每種類型分別實作。同樣,在通用list
物件API上放置特定於字串的方法並沒有太大意義。
看起來很奇怪但在語義上正確的陳述:
[] = ()
是一種語意上正確的語句(將空tuple
包裝到空list
中)'a'[0][0][0][0][0]
在語意上也是正確的,因為Python沒有類似C的字元資料型別單字元字串。3 --0-- 5 == 8
和--5 == 5
都是語意正確的語句,並評估為True
。鑑於a
是一個數字, ++a
和--a
都是有效的python語句,但與C,C ++或Java等語言中的類似語言相比,行為的行為並不相同。
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
解釋:
++
操作員。它實際上是兩個+
運算符。++a
解析為+(+a)
,轉化為a
。同樣,語句的輸出--a
可以是合理的。您必須意識到Python的海像操作員。但是,您是否聽說過太空操作員?
> >> a = 42
> >> a -= - 1
> >> a
43
它被用作替代增量操作員,另一個
> >> a += + 1
> >> a
> >> 44
說明:這種惡作劇來自雷蒙德·赫廷格(Raymond Hettinger)的推文。空間入侵者運算符其實只是一個畸形的a -= (-1)
。相當於a = a - (- 1)
。 a += (+ 1)
情況相似。
Python具有無證件的匡威含義操作員。
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
說明:如果將False
和True
替換為0和1並執行數學,則真相表等效於相反的含義操作員。 (來源)
由於我們正在談論運算符,因此還有@
運算符用於矩陣乘法(不用擔心,這次是真實的)。
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
說明: @
操作員在Python 3.5中加入了 @ the @ operation。任何物件都可以超載__matmul__
魔術方法來定義該操作員的行為。
從Python 3.8開始,您可以使用典型的F-string語法,例如f'{some_var=}
進行快速偵錯。例子,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python在功能中使用2個位元組用於局部變數儲存。從理論上講,這意味著在函數中只能定義65536個變數。但是,python內建了一個方便的解決方案,可用於儲存超過2^16個變數名稱。以下程式碼說明了定義了65536多個本地變數時堆疊中發生的情況(警告:此程式碼在2^18行文字左右列印,因此請準備好!):):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
多個Python線程不會同時運行您的Python程式碼(是的,您聽到了!)。似乎要產生幾個線程並讓它們同時執行您的Python程式碼,這似乎是直觀的,但是,由於Python中的全域解釋器鎖定,您所做的只是使您的線程在同一核心上轉彎執行。 Python執行緒非常適合IO結合任務,但為了實現Python中CPU結合任務的實際並行化,您可能需要使用Python多處理模組。
有時, print
方法可能不會立即列印值。例如,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
由於end
參數,這將在3秒後列印wtfpython
,因為遇到n
或程式完成執行後,輸出緩衝區被沖洗。我們可以透過傳遞flush=True
參數來強迫緩衝區沖洗。
將清單切成範圍的切片,從界限指數列出沒有錯誤
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
切片並不總是會建立一個新物件。例如,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
在Python 3中返回123456789
這是一個有趣的故事,與Python的這種行為有關。
您可以將數位文字與下劃線(以獲得更好的可讀性)與Python 3分開。
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
。這是count
方法的大致實現,這將使事物更加清晰
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
此行為是由於將空的子字串( ''
)與原始字串中的長度為0的匹配所致。
您可以為WTFPYTHON做出貢獻的幾種方式,
請參閱 CONTRIBUTING.md 以了解更多詳細資訊。隨意創建一個新問題來討論事情。
PS:請不要與反向連結請求聯繫,除非它們與項目高度相關,否則不會添加連結。
該系列的想法和設計最初是受Denys Dovhan的出色項目WTFJ的啟發。 Pythonistas的壓倒性支援使它的形狀現在處於形狀。
©Satwik Kansal
如果您喜歡wtfpython,可以使用這些快速連結與您的朋友共享
推特 | LinkedIn | Facebook
我已經收到了WTFPYTHON的PDF(和EPUB)版本的一些請求。您可以在此處添加詳細信息,以便在完成後立即獲取它們。
這就是大家!對於這樣的內容,您可以在此處新增電子郵件。