Python 為什麼只需一條語句“a,b=b,a”,就能直接交換兩個變數?

来源:https://www.cnblogs.com/pythonista/archive/2020/07/14/13302171.html
-Advertisement-
Play Games

從接觸 Python 時起,我就覺得 Python 的元組解包(unpacking)挺有意思,非常簡潔好用。 最顯而易見的例子就是多重賦值,即在一條語句中同時給多個變數賦值: >>> x, y = 1, 2 >>> print(x, y) # 結果:1 2 在此例中,賦值操作符“=”號的右側的兩個數 ...


從接觸 Python 時起,我就覺得 Python 的元組解包(unpacking)挺有意思,非常簡潔好用。

最顯而易見的例子就是多重賦值,即在一條語句中同時給多個變數賦值:

>>> x, y = 1, 2
>>> print(x, y)  # 結果:1 2

在此例中,賦值操作符“=”號的右側的兩個數字會被存入到一個元組中,即變成 (1,2),然後再被解包,依次賦值給“=”號左側的兩個變數。

如果我們直接寫x = 1,2 ,然後列印出 x,或者在“=”號右側寫成一個元組,就能證實到這一點:

>>> x = 1, 2
>>> print(x)     # 結果:(1, 2)
>>> x, y = (1, 2)
>>> print(x, y)  # 結果:1 2

一些博客或公眾號文章在介紹到這個特性時,通常會順著舉一個例子,即基於兩個變數,直接交換它們的值:

>>> x, y = 1, 2
>>> x, y = y, x
>>> print(x, y) # 結果:2 1

一般而言,交換兩個變數的操作需要引入第三個變數。道理很簡單,如果要交換兩個杯子中所裝的水,自然會需要第三個容器作為中轉。

然而,Python 的寫法並不需要藉助中間變數,它的形式就跟前面的解包賦值一樣。正因為這個形式相似,很多人就誤以為 Python 的變數交換操作也是基於解包操作。

但是,事實是否如此呢?

我搜索了一番,發現有人試圖回答過這個問題,但是他們的回答基本不夠全面。(當然,有不少是錯誤的答案,還有更多人只是知其然,卻從未想過要知其所以然)

先把本文的答案放出來吧:Python 的交換變數操作不完全基於解包操作,有時候是,有時候不是!

有沒有覺得這個答案很神奇呢?是不是聞所未聞?!

到底怎麼回事呢?先來看看標題中最簡單的兩個變數的情況,我們上dis 大殺器看看編譯的位元組碼:

上圖開了兩個視窗,可以方便比較“a,b=b,a”與“a,b=1,2”的不同:

  • “a,b=b,a”操作:兩個 LOAD_FAST 是從局部作用域中讀取變數的引用,並存入棧中,接著是最關鍵的 ROT_TWO 操作,它會交換兩個變數的引用值,然後兩個 STORE_FAST 是將棧中的變數寫入局部作用域中。
  • “a,b=1,2”操作:第一步 LOAD_CONST 把“=”號右側的兩個數字作為元組放到棧中,第二步 UNPACK_SEQUENCE 是序列解包,接著把解包結果寫入局部作用域的變數上。

很明顯,形式相似的兩種寫法實際上完成的操作並不相同。在交換變數的操作中,並沒有裝包和解包的步驟!

ROT_TWO 指令是 CPython 解釋器實現的對於棧頂兩個元素的快捷操作,改變它們指向的引用對象。

還有兩個類似的指令是 ROT_THREE 和 ROT_FOUR,分別是快捷交換三和四個變數(摘自:ceval.c 文件,最新的 3.9 分支):

預定義的棧頂操作如下:

查看官方文檔中對於這幾個指令的解釋,其中 ROT_FOUR 是 3.8 版本新加的:

  • ROT_TWO

    Swaps the two top-most stack items.

  • ROT_THREE

    Lifts second and third stack item one position up, moves top down to position three.

  • ROT_FOUR

    Lifts second, third and forth stack items one position up, moves top down to position four.
    New in version 3.8.

CPython 應該是以為這幾種變數的交換操作很常見,因此才提供了專門的優化指令。就像 [-5,256] 這些小整數被預先放到了整數池裡一樣。

對於更多變數的交換操作,實際上則會用到前面說的解包操作:

截圖中的 BUILD_TUPLE 指令會將給定數量的棧頂元素創建成元組,然後被 UNPACK_SEQUENCE 指令解包,再依次賦值。

值得一提的是,此處之所以比前面的“a,b=1,2”多出一個 build 操作,是因為每個變數的 LOAD_FAST 需要先單獨入棧,無法直接被組合成 LOAD_CONST 入棧。也就是說,“=”號右側有變數時,不會出現前文中的 LOAD_CONST 一個元組的情況。

最後還有一個值得一提的細節,那幾個指令是跟棧中元素的數量有關,而不是跟賦值語句中實際交換的變數數有關。看一個例子就明白了:

分析至此,你應該明白前文中的結論是怎麼回事了吧?

我們稍微總結一下:

  • Python 能在一條語句中實現多重賦值,這是利用了序列解包的特性
  • Python 能在一條語句中實現變數交換,不需引入中間變數,在變數數少於 4 個時(3.8 版本起是少於 5 個),CPython 是利用了 ROT_* 指令來交換棧中的元素,當變數數超出時,則是利用了序列解包的特性。
  • 序列解包是 Python 的一大特性,但是在本文的例子中,CPython 解釋器在小小的操作中還提供了幾個優化的指令,這絕對會超出大多數人的認知

如果你覺得本文分析得不錯,那你應該會喜歡這些文章:

1、Python為什麼使用縮進來劃分代碼塊?

2、Python 的縮進是不是反人類的設計?

3、Python 為什麼不用分號作語句終止符?

4、Python 為什麼沒有 main 函數?為什麼我不推薦寫 main 函數?

5、Python 為什麼推薦蛇形命名法?

6、Python 為什麼不支持 i++ 自增語法,不提供 ++ 操作符?

寫在最後:本文屬於“Python為什麼”系列(Python貓出品),該系列主要關註 Python 的語法、設計和發展等話題,以一個個“為什麼”式的問題為切入點,試著展現 Python 的迷人魅力。部分話題會推出視頻版,請在 B 站收看,觀看地址:視頻地址

公眾號【Python貓】, 本號連載優質的系列文章,有Python為什麼系列、喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫作、優質英文推薦與翻譯等等,歡迎關註哦。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、px是相對長度單位,它是相對於顯示器屏幕解析度而言的。 優缺點:比較穩定和精確,但在瀏覽器中放大或縮放瀏覽頁面時會出現頁面混亂的情況。 2、em是相對長度單位,EM是相對於父元素來設計字體大小的。如果當前對行內文本的字體尺寸未被人為設置,則相對於瀏覽器的預設字體尺寸。 優缺點:em的值並不是固定 ...
  • 實現步驟 html + bootstrap 佈局畫3個面板。 註:面板樣式 position 屬性必須是絕對位置或者相對位置。 監聽面板的的 mousedown事件。 記錄當前對應面板的位置target_index,設置面板透明拖動。 監聽當前被拖動的面板的mousemove事件。 根據滑鼠移動的位 ...
  • 我們與企業內部的Web開發團隊進行了很多次交流,研究了很長時間,最後將Debug工具與Web前端開發工具整理彙總在了一起,這些工具對每個Web開發人員都非常有用。 這些工具將使您的工作更加輕鬆,特別是如果您是Web開發人員,Web設計人員或項目經理。您應該查看一下並嘗試一下。 不管您是網站開發人員還 ...
  • 用形狀、大小完全相同的一種或幾種平面圖形進行拼接,彼此之間不留空隙、不重疊地鋪成一片,就叫做這幾種圖形的平面鑲嵌。 1.用一種多邊形實現的平面鑲嵌圖案 我們可以採用正三角形、正方形或正六邊形實現平面鑲嵌。 (1)用正方形平鋪。 用正方形進行平面鑲嵌比較簡單,編寫如下的HTML代碼。 <!DOCTYP ...
  • Properties標簽 第一種:全局配置文件內部配置數據源信息 (1)在全局配置文件中編寫數據源信息 <properties> <!--name指定數據源名稱,value指定其值--> <property name="driver" value="com.mysql.jdbc.Driver"/> ...
  • 生產者-消費者算是併發編程中常見的問題。依靠緩衝區我們可以實現生產者與消費者之間的解耦。生產者只管往緩衝區裡面放東西,消費者只管往緩衝區裡面拿東西。這樣我們避免生產者想要交付數據給消費者,但消費者此時還無法接受數據這樣的情況發生。 wait notify 這個問題其實就是線程間的通訊,所以要註意的是 ...
  • Django框架的基本使用 Django是一個功能強大的web框架 框架模式 1、MVC和MTV框架 MVC:Web伺服器開發領域里著名的MVC模式,所謂MVC就是把Web應用分為模型(M),控制器(C)和視圖(V)三層,結構說明如下: M: models 資料庫相關操作 V: views 視圖,也 ...
  • import os import fnmatch ls=os.listdir(r"E:\pdf") #列出文件夾下所有的文件 for 文件名 in ls: if fnmatch.fnmatch(文件名,"[!a-z].pdf"): print(文件名) 1.pdf2.pdf3.pdf4.pdf>>> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...