前言
增強型賦值語句是經常被使用到的,因為從各種學習渠道中,我們能夠得知i += 1
的效率往往要比 i = i + 1
更高一些(這里以 += 為例,實際上增強型賦值語句不僅限於此)。所以我們會樂此不疲的在任何能夠替換普通賦值語句的地方使用增量型賦值語句,以此來優化代碼。那么我們是否有想過,在什么情況下 i += 1
其實並不等效於 i = i + 1
!!
增強型賦值語句:
>>> a = [1, 2, 3]
>>> b = a
>>> b += [4, 5, 6]
>>> a
[1, 2, 3, 4, 5, 6]
>>> b
[1, 2, 3, 4, 5, 6]
>>> id(a)
140268862690880
>>> id(b)
140268862690880
>>>
代碼解析:先定義了個列表a,然后創建對象b,b的地址指向a,所以a和b共用一片內存地址,b += [4, 5, 6]
因為list是可變對象,所以b仍然在原來的內存地址上,只是改變了b的value,又因為a和b是指向同一地址的,所以a和b的值相等
普通賦值語句:
>>> a = [1, 2, 3]
>>> b = a
>>> b = b + [4, 5, 6]
>>> a
[1, 2, 3]
>>> b
[1, 2, 3, 4, 5, 6]
>>> id(a)
140268888586672
>>> id(b)
140268866910800
>>>
代碼解析:先定義了個列表a,然后創建對象b,b的地址指向a,目前a和b共用一片內存地址,關鍵點:b = b + [4, 5, 6]
,是在原來b的基礎上,添加了一個列表,並且將新的值賦值給了左邊的b
,原先b的內存地址是指向a的,但是現在又重新賦值了,所以b重新開辟了一片新的內存地址,此時a和b的id和value均不同
這是一個值得注意的坑,警惕我們在使用增量賦值運算符來操作可變對象(如:列表)時可能會產生不可預測的結果。
增值運算符和普通運算符對於不可變對象作用一致
上面我們說的都是針對可變對象,但是針對不可變對象比如元組,他們都會產生新的內存地址
>>> a = (1, 2, 3)
>>> id(a)
140393063791584
>>> a += (4, )
>>> a
(1, 2, 3, 4)
>>> id(a)
140393063931056
>>> b = (1, 2, 3)
>>> id(b)
140393064025216
>>> b = b + (4, )
>>> b
(1, 2, 3, 4)
>>> id(b)
140393063930864
>>>
總結
要解釋這個問題,首先需要了解「Python 共享引用」的概念:在 Python 中,允許若干個不同的變量引用指向同一個內存對象
。同時在前文中也提到,增強賦值語句比普通賦值語句的效率更高,這是因為在 Python 源碼中, 增強賦值比普通賦值多實現了“寫回”的功能,也就是說增強賦值在條件符合的情況下(例如:操作數是一個可變類型對象)會以追加的方式來進行處理,而普通賦值則會以新建
的方式進行處理。這一特點導致了增強賦值語句中的變量對象始終只有一個,Python 解析器解析該語句時不會額外創建出新的內存對象
。所以例一中變量 a、b 的引用在最后依舊指向了同一個內存對象;相反,對於普通賦值運算語句,Python 解析器無法分辨語句中的兩個同名變量(例如:b = b + 1)是否應該為同一內存對象,所以干脆再創建出一個新的內存對象用來存放最后的運算結果,所以例二中的 a、b 從原來指向同一內存對象,到最后分別指向了兩個不同的內存對象。
提示:盡量不要使用增量賦值運算符來處理任何可變類型對象,除非你對上述問題有了足夠的了解。