摘要:常用的模塊方法接收參數為一個代碼塊可以是模塊,類,方法,函數,或者是對象,可以得到這個代碼塊對應的字節碼指令序列。具體驗證可以分析下兩種方法的字節碼指令。
交換兩個變量的值,大家最常見的寫法是這樣的:
>>> temp = x >>> x = y >>> y = temp
但其實更 Pythonic 的寫法是這樣的:
>>> x, y = y, x
大家有沒有想過為什么在 Python 中可以這樣交換兩個變量的值?
Python 代碼是先解釋(這里的解釋是相對編譯而言的,Python 不同與 C/C++ 之類的編譯型語言,是需要從源文件編譯成機器指令)成 Python 字節碼(byte code, .pyc文件主要是用來存儲這些字節碼的)之后,再由 Python 解釋器來執行這些字節碼的。一般來說,Python 語句會對應若干字節碼指令,Python 的字節碼類似于匯編指令的中間語言,但是一個字節碼并不只是對應一個機器指定。
內置模塊 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.
dis.dis 接收參數為一個代碼塊(可以是模塊,類,方法,函數,或者是對象),可以得到這個代碼塊對應的字節碼指令序列。
>>> 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
輸出的格式分別是:行號,地址,指令,操作參數, 參數解釋(識別變量名稱,常量值等)
切入正題, 我們直接來看下第二種寫法的字節碼指令:
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_CONST 和 STORE_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
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37631.html
摘要:本次分享講簡單聊聊函數的用法,希望能給讀者一點啟示和參考。在最后的語句中,我們給出了的值,并且值重復,函數接收后面一個值,且值傳遞不起作用,因此輸出結果為本次分享到此結束。本文介紹了幾個函數使用的例子,希望能拋磚引玉,也歡迎大家多多交流 ??在Python中,exec()是一個十分有趣且實用的內置函數,不同于eval()函數只能執行計算數學表達式的結果的功能,exec()能夠動態地執行...
摘要:而引用類型值是指那些保存堆內存中的對象,意思是變量中保存的實際上只是一個指針,這個指針指向內存中的另一個位置,該位置保存對象。而堆內存主要負責對象這種變量類型的存儲。我們需要明確一點,深拷貝與淺拷貝的概念只存在于引用類型。 深拷貝和淺拷貝 說起深拷貝和淺拷貝,首先我們來看兩個栗子 // 栗子1 var a = 1,b=a; console.log(a); console.log(b) ...
摘要:返回值是一個經過排序的可迭代類型,與一樣。注一般來說,和可以使用表達式。與的不同在于,是在原位重新排列列表,而是產生一個新的列表。 我們需要對List進行排序,Python提供了兩個方法 對給定的List L進行排序,方法1.用List的成員函數sort進行排序方法2.用built-in函數sorted進行排序(從2.4開始) ----------------------------...
摘要:但是實際寫程序中,我們經常會寫出許多繁雜的丑陋的代碼。特別推薦,許多代碼讓我獲益匪淺,比如這里對的使用。用可以寫出很簡單直觀的代碼,如下當然,上面不考慮效率,這里有一個利用分治法思想的高效的方法。更多文章更多閱讀中參數的用法高級編程技巧 用 Python 時間也算不短了,但總感覺自己在用寫 C++ 代碼的思維寫 Python,沒有真正用到其作為腳本語言的優勢。之前刷 LeetCode ...
閱讀 1298·2021-11-16 11:44
閱讀 3766·2021-10-09 10:01
閱讀 1751·2021-09-24 10:31
閱讀 3845·2021-09-04 16:41
閱讀 2517·2021-08-09 13:45
閱讀 1217·2019-08-30 14:08
閱讀 1780·2019-08-29 18:32
閱讀 1644·2019-08-26 12:12