动态

详情 返回 返回

一個關於 += 的謎題 - 动态 详情

原文鏈接: 一個關於 += 的謎題

今天在看書過程中發現了一個問題,還挺有意思的,分享給大家。

下面兩個 Python 表達式會產生什麼結果?

t = (1, 2, [3, 4])
t[2] += [5, 6]

給四個備選答案:

  1. t 變成 (1, 2, [3, 4, 5, 6])
  2. 因為 tuple 不支持對它的元素賦值,所以會拋出 TypeError 異常。
  3. 以上兩個都不是。
  4. 以上兩個都是對的。

當時看到這個問題,第一反應就是選 2。因為 tuple 是不可變對象,不支持對它的元素賦值,會報錯。

但事實上,這道題的正解是 4。

在終端裏驗證一下:

Python 3.8.2 (default, Oct  2 2020, 10:45:42)
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> t = (1, 2, [3, 4])
>>> t[2] += [5, 6]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

結果是沒問題的,t 被修改了,但是也報錯了。

還可以在 Python Tutor 上分析一下:

網站地址: https://pythontutor.com/

這個網站可以可視化分析 Python 的運行過程和原理。

執行第一個表達式:

執行第二個表達式:

為什麼會這樣呢?可以從兩個方面來解釋:

一、對象類型

Python 中的對象可以分成兩類,可變對象和不可變對象,比如一些內置類型:

  1. 可變對象:list,set,dict。
  2. 不可變對象:int,float,bool,string,tuple。

舉一個例子:

可變對象:

>>> a = [1, 2, 3]
>>> id(a)
2139167246856
>>> b = a
>>> id(b)
2139167246856
>>> a[1] = 4
>>> a
[1, 4, 3]
>>> b
[1, 4, 3]
>>> id(a)
2139167246856
>>> id(b)
2139167246856

可以看到,改變 a 的同時 b 也跟着變,因為他們始終指向同一個地址。

不可變對象:

>>> a = (1, 2, 3)
>>> id(a)
2139167074776
>>> b = a
>>> a = (4, 5, 6)
>>> a
(4, 5, 6)
>>> b
(1, 2, 3)
>>> id(a)
2139167075928
>>> id(b)
2139167074776

可以看到,a 的值改變後,它的地址也發生了變化,而 b 還是原來的地址,並且原地址中的內容也沒有發生變化。

二、字節碼

首先解釋一下字節碼是什麼?

Python 執行程序時會把源碼文件編譯成字節碼文件,存放在 __pycahe 目錄內,文件用 .pyc 結尾。之後如果不再修改源碼文件,運行時則直接使用 .pyc 文件編譯成機器碼,這樣不但運行速度快,而且支持多個操作系統。

字節碼,其實就是一種中間代碼。

下面用 dis 模塊來看一下表達式 s[a] += b 的執行過程:

>>> import dis
>>> dis.dis('s[a] += b')
  1           0 LOAD_NAME                0 (s)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
>>>

通過分析字節碼,可以看到其中的關鍵三步:

  1. 4 DUP_TOP_TWO:將 s[a] 存入 TOS(Top Of Stack)。
  2. 10 INPLACE_ADD:執行 TOS += b,帶入到文章開頭的表達式,就相當於向 t[2] 中添加元素,因為 t[2] 是 list,可變對象,所以這一操作沒有問題。
  3. 14 STORE_SUBSCR:將結果保存回 s[a] = TOS,這相當於將結果重新賦值回 t,由於 t 是 tuple,不可變對象,所以報錯。

雖然這個問題在平時開發中可能並不常見,但通過分析還是有不少知識點可以深挖的。

簡單總結以下三點:

  1. 不要把可變對象放在元組裏面。
  2. 增量賦值不是一個原子操作。我們剛才也看到了,它雖然拋出了異常,但還是完成了操作。
  3. 查看 Python 的字節碼並不難,而且它對我們瞭解代碼背後的運行機制很有幫助。

以上就是本文的全部內容,如果覺得還不錯的話,歡迎點贊轉發,多謝


推薦閲讀:

  • 計算機經典書籍(含下載方式)
  • 技術博客: 硬核後端開發技術乾貨,內容包括 Python、Django、Docker、Go、Redis、ElasticSearch、Kafka、Linux 等。
  • Go 程序員: Go 學習路線圖,包括基礎專欄,進階專欄,源碼閲讀,實戰開發,面試刷題,必讀書單等一系列資源。
  • 面試題彙總: 包括 Python、Go、Redis、MySQL、Kafka、數據結構、算法、編程、網絡等各種常考題。

Add a new 评论

Some HTML is okay.