淺談 (x, y) = (y, x)

交換兩個變量的值,你們最多見的寫法是這樣的:html

>>> temp = x
>>> x = y
>>> y = temp

但其實更 Pythonic 的寫法是這樣的:python

>>> x, y = y, x

你們有沒有想過爲何在 Python 中能夠這樣交換兩個變量的值?sass

Python 代碼是先解釋(這裏的解釋是相對編譯而言的,Python 不一樣與 C/C++ 之類的編譯型語言,是須要從源文件編譯成機器指令)成 Python 字節碼(byte code, .pyc文件主要是用來存儲這些字節碼的)以後,再由 Python 解釋器來執行這些字節碼的。通常來講,Python 語句會對應若干字節碼指令,Python 的字節碼相似於彙編指令的中間語言,可是一個字節碼並不僅是對應一個機器指定。ide

內置模塊 dis 能夠用來分析字節碼。DOC函數

The dis module supports the analysis of CPython bytecode by disassembling it. The CPython bytecode which this module takes as an input is defined in the file Include/opcode.h and used by the compiler and the interpreter.性能

經常使用的 dis 模塊方法: dis.dis([bytesource])測試

dis.dis([bytesource])
Disassemble the bytesource object. bytesource can denote either a module, a class, a method, a function, or a code object. For a module, it disassembles all functions. For a class, it disassembles all methods. For a single code sequence, it prints one line per bytecode instruction. If no object is provided, it disassembles the last traceback.ui

dis.dis 接收參數爲一個代碼塊(能夠是模塊,類,方法,函數,或者是對象),能夠獲得這個代碼塊對應的字節碼指令序列。this

>>> import dis
>>> def test():
...     a = 1
...     
... 
>>> dis.dis(test)
  3           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

輸出的格式分別是:行號,地址,指令,操做參數, 參數解釋(識別變量名稱,常量值等)code

切入正題, 咱們直接來看下第二種寫法的字節碼指令:

swap_2.py

x = 1
y = 3
x, y = y, x

python -m dis swap_2.py

1           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (x)

  2           6 LOAD_CONST               1 (3)
              9 STORE_NAME               1 (y)

  3          12 LOAD_NAME                1 (y)
             15 LOAD_NAME                0 (x)
             18 ROT_TWO
             19 STORE_NAME               0 (x)
             22 STORE_NAME               1 (y)
             25 LOAD_CONST               2 (None)
             28 RETURN_VALUE

部分字節碼指令以下,具體的指令請移步官網

LOAD_CONST(consti)
Pushes co_consts[consti] onto the stack.
STORE_NAME(namei)
Implements name = TOS. namei is the index of name in the attribute co_names of the code object. The compiler tries to use STORE_FAST or STORE_GLOBAL if possible.
LOAD_NAME(namei)
Pushes the value associated with co_names[namei] onto the stack.
ROT_TWO()
Swaps the two top-most stack items.

解釋下上面的字節碼指令:

第一行執行兩個字節碼指令, 分別是LOAD_CONSTSTORE_NAME,執行的動做是將 co_consts[0] 壓棧(也就是常量表的第一個常量,整數1壓入棧中),而後獲取co_names[0]的變量名x(變量名錶的第一個名字),棧頂元素(整數1)出棧和co_names[0]存儲到f->f_locals。

第二行的執行方式如同第一行。

co_consts[0] = 1
co_names[0] = x
f->f_locals['x'] = 1

co_consts[1] = 3
co_names[1] = y
f->f_locals['y'] = 3

重點在第三行,前兩行的計算順序都是從友往左進行的(通常狀況下, Python 表達式的計算順序是從左到右,可是在表達式賦值的時候,表達式右邊的操做數優先於左邊),也就是說,第四行是這樣執行的,先建立元組(y, x),執行的動做是兩個 LOAD_NAME,會依次搜索local,global,builtin名字空間中的co_names[1](對應變量名y)和co_names[0](對應變量名x)並把相對應的值壓棧。接下去執行的動做是交換ROT_TWO, 交換棧頂的兩個元素位置。

從下一個執行指令就能夠看出來,先獲取co_names[0]的變量名x,棧頂元素(如今是原先y的值)出棧並儲存,兩次存儲就實現了交換兩個變量的值。

第二種方法不借助任何中間變量而且可以得到更好的性能。咱們能夠簡單測試下:

>>> from timeit import Timer
>>> Timer('temp = x;x = y;y = temp', 'x=2;y=3').timeit()
0.030814170837402344
>>> Timer('x, y = y, x', 'x=2;y=3').timeit()
0.027340173721313477

爲何第二種方法消耗的時間更少呢?能夠猜想一下,是中間變量賦值引發的耗時。具體驗證能夠分析下兩種方法的字節碼指令。

Life such short,be Pythonic .

Blog : JunNplus

相關文章
相關標籤/搜索