在本篇文章當中主要給大家介紹在 cpython 當中一些比較常見的位元組碼,從根本上理解 python 程式的執行。在本文當中主要介紹一些 python 基本操作的位元組碼,並且將從位元組碼的角度分析函數裝飾器的原理!
這個指令用於將一個常數載入到棧中。常數可以是數位、字串、元組、列表、字典等物件。例如:
>>> dis.dis(lambda: 42)
1 0 LOAD_CONST 1 (42)
2 RETURN_VALUE
這個指令用於將一個變數載入到棧中。例如:
>>> dis.dis(lambda: x)
1 0 LOAD_GLOBAL 0 (x)
2 RETURN_VALUE
>>>
這個指令用於將棧頂的值儲存到一個變數中。例如:
>>> dis.dis("x=42")
1 0 LOAD_CONST 0 (42)
2 STORE_NAME 0 (x)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
這個指令用於對棧頂的兩個值進行加法運算並將結果推播到棧中。
>>> dis.dis(lambda: x + y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_ADD
6 RETURN_VALUE
這個指令用於對棧頂的兩個值進行減法運算並將結果推播到棧中。
>>> dis.dis(lambda: x - y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_SUBTRACT
6 RETURN_VALUE
同樣的加減乘除取餘數的位元組碼如下所示:
>>> dis.dis(lambda: x + y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_ADD
6 RETURN_VALUE
>>> dis.dis(lambda: x - y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_SUBTRACT
6 RETURN_VALUE
>>> dis.dis(lambda: x * y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_MULTIPLY
6 RETURN_VALUE
>>> dis.dis(lambda: x / y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_TRUE_DIVIDE
6 RETURN_VALUE
>>> dis.dis(lambda: x // y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_FLOOR_DIVIDE
6 RETURN_VALUE
>>> dis.dis(lambda: x % y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_MODULO
6 RETURN_VALUE
這個指令用於比較棧頂的兩個值,並且將比較得到的結果壓入棧中,這個位元組碼後面後一個位元組的引數,表示小於大於不等於等等比較符號。例如:
>>> dis.dis(lambda: x - y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 BINARY_SUBTRACT
6 RETURN_VALUE
>>> dis.dis(lambda: x > y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 4 (>)
6 RETURN_VALUE
>>> dis.dis(lambda: x < y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 0 (<)
6 RETURN_VALUE
>>> dis.dis(lambda: x != y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 3 (!=)
6 RETURN_VALUE
>>> dis.dis(lambda: x <= y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 1 (<=)
6 RETURN_VALUE
>>> dis.dis(lambda: x >= y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 5 (>=)
6 RETURN_VALUE
>>> dis.dis(lambda: x == y)
1 0 LOAD_GLOBAL 0 (x)
2 LOAD_GLOBAL 1 (y)
4 COMPARE_OP 2 (==)
6 RETURN_VALUE
將棧頂元素彈出作為返回值。
這個指令用於建立一個列表。例如:
>>> dis.dis(lambda: [a, b, c, e])
1 0 LOAD_GLOBAL 0 (a)
2 LOAD_GLOBAL 1 (b)
4 LOAD_GLOBAL 2 (c)
6 LOAD_GLOBAL 3 (e)
8 BUILD_LIST 4
10 RETURN_VALUE
這條位元組碼指令有一個參數列示棧空間當中列表元素的個數,在上面的例子當中這個引數是 4 。
這個指令用於建立一個元組。例如:
>>> dis.dis(lambda: (a, b, c))
1 0 LOAD_GLOBAL 0 (a)
2 LOAD_GLOBAL 1 (b)
4 LOAD_GLOBAL 2 (c)
6 BUILD_TUPLE 3
8 RETURN_VALUE
同樣的這個位元組碼也有一個引數,表示建立元組的元素個數。
這個指令用於建立一個字典。例如:
和 list 和 tuple 一樣,這條指令是用於建立一個集合物件,同樣的這條指令也有一個參數列示用於建立集合的元素的個數。
>>> dis.dis(lambda: {a, b, c, d})
1 0 LOAD_GLOBAL 0 (a)
2 LOAD_GLOBAL 1 (b)
4 LOAD_GLOBAL 2 (c)
6 LOAD_GLOBAL 3 (d)
8 BUILD_SET 4
10 RETURN_VALUE
這條指令是用於建立一個字典物件,同樣的這條指令也有一個引數,表示字典當中元素的個數。
>>> dis.dis(lambda: {1:2, 3:4})
1 0 LOAD_CONST 1 (2)
2 LOAD_CONST 2 (4)
4 LOAD_CONST 3 ((1, 3))
6 BUILD_CONST_KEY_MAP 2
8 RETURN_VALUE
如果你是一個 pythoner 那麼你肯定或多或少聽說過裝飾器,這是一個 python 的語法糖我們可以用它來做很多有趣的事情,比如在不修改原始碼的基礎之上給函數附加一些功能,比如說計算時間。
import time
def eval_time(func):
def cal_time(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs)
end = time.time()
return r, end - start
return cal_time
@eval_time
def fib(n):
a = 0
b = 1
while n > 0:
n -= 1
a, b = b, a + b
return a
在上面的程式碼當中我們實現了一個計算斐波拉契數列的函數,除此之外還寫了一個 eval_time 函數用於計算函數執行的時間,現在呼叫函數 fib(10),程式的輸出如下所示:
>>>fib(10)
(55, 5.9604644775390625e-06)
可以看到實現了我們想要的效果。
現在我們使用一個更加簡單的例子來模擬上面的程式碼結構,方便我們對上面函數執行的過程進行分析:
s = """
def decorator(func):
print("Hello")
return func
@decorator
def fib(n):
pass
"""
dis.dis(s)
上面的 dis 函數的輸出對應程式碼的位元組碼如下所示:
2 0 LOAD_CONST 0 (<code object decorator at 0x108068d40, file "<dis>", line 2>)
2 LOAD_CONST 1 ('decorator')
4 MAKE_FUNCTION 0
6 STORE_NAME 0 (decorator)
6 8 LOAD_NAME 0 (decorator)
7 10 LOAD_CONST 2 (<code object fib at 0x1075c1710, file "<dis>", line 6>)
12 LOAD_CONST 3 ('fib')
14 MAKE_FUNCTION 0
16 CALL_FUNCTION 1
18 STORE_NAME 1 (fib)
20 LOAD_CONST 4 (None)
22 RETURN_VALUE
Disassembly of <code object decorator at 0x108068d40, file "<dis>", line 2>:
3 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello')
4 CALL_FUNCTION 1
6 POP_TOP
4 8 LOAD_FAST 0 (func)
10 RETURN_VALUE
Disassembly of <code object fib at 0x1075c1710, file "<dis>", line 6>:
8 0 LOAD_CONST 0 (None)
2 RETURN_VALUE
看到這裡就能夠理解了原來裝飾器的最根本的原理不就是函數呼叫嘛,比如我們最前面的用於計算函數執行時間的裝飾器的原理就是:
fib = eval_time(fib)
將 fib 函數作為 eval_time 函數的引數,再將這個函數的返回值儲存到 fib 當中,當然這個物件必須是可呼叫的,不然後面使用 fib() 就會儲存,我們可以使用下面的程式碼來驗證這個效果。
def decorator(func):
return func()
@decorator
def demo():
return "function demo return string : Demo"
print(demo)
執行上面的程式結果為:
function demo return string : Demo
可以看到 demo 已經變成了一個字串物件而不再是一個函數了,因為 demo = decorator(demo)
,而在函數 decorator 當中返回值是 demo 函數自己的返回值,因此才列印了字串。
在本篇文章當中主要給大家介紹了 python 當中一些基礎的位元組碼對應的含義以及範例程式碼,本篇文章最重要的便是從位元組碼的角度解釋了裝飾器的本質原理,這對我們以後使用裝飾器非常有幫助,可以靈活的控制和了解裝飾器其中發生的故事。
本篇文章是深入理解 python 虛擬機器器系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython
更多精彩內容合集可存取專案:https://github.com/Chang-LeHung/CSCore
關注公眾號:一無是處的研究僧,瞭解更多計算機(Java、Python、計算機系統基礎、演演算法與資料結構)知識。