精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Python 源文件編譯之后會得到什么,它的結構是怎樣的?和字節碼又有什么聯系?

開發 前端
Python 的函數、類、模塊等,都具有各自的作用域,每個作用域對應一個獨立的代碼塊,在編譯時,Python 編譯器會為每個代碼塊都創建一個 PyCodeObject 對象。

楔子

當我們執行一個 py 文件的時候,只需要在命令行中輸入 python xxx.py 即可,但你有沒有想過這背后的流程是怎樣的呢?

首先 py 文件不是一上來就直接執行的,而是會先有一個編譯的過程,整個步驟如下:

圖片圖片

這里我們看到了 Python 編譯器、Python 虛擬機,而且我們平常還會說 Python 解釋器,那么三者之間有什么區別呢?

圖片圖片

Python 編譯器負責將 Python 源代碼編譯成 PyCodeObject 對象,然后交給 Python 虛擬機來執行。

那么 Python 編譯器和 Python 虛擬機都在什么地方呢?如果打開 Python 的安裝目錄,會發現有一個 python.exe,點擊的時候會通過它來啟動一個終端。

圖片

但問題是這個文件大小還不到 100K,不可能容納一個編譯器加一個虛擬機,所以下面還有一個 python312.dll。沒錯,編譯器、虛擬機都藏身于 python312.dll 當中。

因此 Python 雖然是解釋型語言,但也有編譯的過程。源代碼會被編譯器編譯成 PyCodeObject 對象,然后再交給虛擬機來執行。而之所以要存在編譯,是為了讓虛擬機能更快速地執行,比如在編譯階段常量都會提前分配好,而且還可以盡早檢測出語法上的錯誤。

pyc 文件是什么

在 Python 開發時,我們肯定都見過這個 pyc 文件,它一般位于 __pycache__ 目錄中,那么 pyc 文件和 PyCodeObject 之間有什么關系呢?

首先我們都知道字節碼,虛擬機的執行實際上就是對字節碼不斷解析的一個過程。然而除了字節碼之外,還應該包含一些其它的信息,這些信息也是 Python 運行的時候所必需的,比如常量、變量名等等。

我們常聽到 py 文件被編譯成字節碼,這句話其實不太嚴謹,因為字節碼只是一個 PyBytesObject 對象、或者說一段字節序列。但很明顯,光有字節碼是不夠的,還有很多的靜態信息也需要被收集起來,它們整體被稱為 PyCodeObject。

而 PyCodeObject 對象中有一個字段 co_code,它是一個指針,指向了這段字節序列。但是這個對象除了有 co_code 指向的字節碼之外,還有很多其它字段,負責保存代碼涉及到的常量、變量(名字、符號)等等。

所以雖然編寫的是 py 文件,但虛擬機執行的是編譯后的 PyCodeObject 對象。但是問題來了,難道每一次執行都要將源文件編譯一遍嗎?如果沒有對源文件進行修改的話,那么完全可以使用上一次的編譯結果。相信此時你能猜到 pyc 文件是干什么的了,它就是負責保存編譯之后的 PyCodeObject 對象。

現在我們知道了,pyc 文件里面保存的內容是 PyCodeObject 對象。對于 Python 編譯器來說,PyCodeObject 對象是對源代碼編譯之后的結果,而 pyc 文件則是這個對象在硬盤上的表現形式。

當下一次運行的時候,Python 解釋器會根據 pyc 文件中記錄的編譯結果,直接建立內存中的 PyCodeObject 對象,而不需要再重新編譯了,當然前提是沒有對源文件進行修改。

PyCodeObject 底層結構

既然 PyCodeObject 對象是源代碼的編譯結果,那么搞清楚它的底層結構就至關重要,下面來看一下它長什么樣子。相比以前的版本(比如 3.8),結構變化還是有一點大的。

// Include/pytypedefs.h
typedef struct PyCodeObject PyCodeObject;

// Include/cpython/code.h
struct PyCodeObject _PyCode_DEF(1);

#define _PyCode_DEF(SIZE) {                   \
    PyObject_VAR_HEAD                         \
                                              \
    PyObject *co_consts;                      \
    PyObject *co_names;                       \
    PyObject *co_exceptiontable;              \
    int co_flags;                             \
    int co_argcount;                          \
    int co_posonlyargcount;                   \
    int co_kwonlyargcount;                    \
    int co_stacksize;                         \
    int co_firstlineno;                       \
    int co_nlocalsplus;                       \
    int co_framesize;                         \
    int co_nlocals;                           \
    int co_ncellvars;                         \
    int co_nfreevars;                         \
    uint32_t co_version;                      \
    PyObject *co_localsplusnames;             \
    PyObject *co_localspluskinds;             \
    PyObject *co_filename;                    \
    PyObject *co_name;                        \
    PyObject *co_qualname;                    \
    PyObject *co_linetable;                   \
    PyObject *co_weakreflist;                 \
    _PyCoCached *_co_cached;                  \
    uint64_t _co_instrumentation_version;     \
    _PyCoMonitoringData *_co_monitoring;      \
    int _co_firsttraceable;                   \
    void *co_extra;                           \
    char co_code_adaptive[(SIZE)];            \
}

這里面的每一個字段,我們一會兒都會詳細介紹,并通過代碼逐一演示??傊?Python 編譯器在對源代碼進行編譯的時候,針對每一個 code block(代碼塊),都會創建一個 PyCodeObject 與之對應。

但多少代碼才算得上是一個 block 呢?事實上,Python 有一個簡單而清晰的規則:當進入一個新的名字空間,或者說作用域時,就算是進入了一個新的 block 了。舉個例子:

class A:
    a = 123

def foo():
    a = []

我們仔細觀察一下上面這段代碼,它在編譯完之后會有三個 PyCodeObject 對象,一個是對應整個 py 文件(模塊)的,一個是對應 class A 的,一個是對應 def foo 的。因為這是三個不同的作用域,所以會有三個 PyCodeObject 對象。

所以一個 code block 對應一個作用域、同時也對應一個 PyCodeObject 對象。Python 的類、函數、模塊都有自己獨立的作用域,因此在編譯時也都會有一個 PyCodeObject 對象與之對應。

PyCodeObject 字段解析

PyCodeObject 我們知道它是干什么的了,那如何才能拿到這個對象呢?首先該對象在 Python 里面的類型是 <class 'code'>,但是底層沒有將這個類暴露給我們,因此 code 這個名字在 Python 里面只是一個沒有定義的變量罷了。

但我們可以通過其它的方式進行獲取,比如函數。

def func():
    pass

print(func.__code__)  # <code object ......
print(type(func.__code__))  # <class 'code'>

我們可以通過函數的 __code__ 屬性拿到底層對應的 PyCodeObject 對象,當然也可以獲取里面的字段,我們來演示一下,并詳細介紹每個字段的含義。

PyObject_VAR_HEAD:變長對象的頭部信息

我們看到 Python 真的一切皆對象,源代碼編譯之后的結果也是一個對象。

co_consts:常量池,一個元組,保存代碼塊中創建的所有常量

def foo():
    a = 123
    b = "hello"
    c = (1, 2)
    d = ["x", "y"]
    e = {"p": "k"}
    f = {7, 8}

print(foo.__code__.co_consts)
"""
(None, 123, 'hello', (1, 2), 'x', 'y', 'p', 'k', 7, 8)
"""

co_consts 里面出現的都是編譯階段可以確定的常量,而 ["x", "y"] 和 {"p": "k"} 沒有出現,由此我們可以得出,列表和字典絕不是在編譯階段構建的。編譯時,只是收集了里面的元素,然后等到運行時再去動態構建。

不過問題來了,在構建的時候解釋器怎么知道是要構建列表、還是字典、亦或是其它的什么對象呢?所以這就依賴于字節碼了,解釋字節碼的時候,會判斷到底要構建什么樣的對象。

因此解釋器執行的是字節碼,核心邏輯都體現在字節碼中。但是光有字節碼還不夠,它包含的只是程序的主干邏輯,至于變量、常量,則從符號表和常量池里面獲取。

然后還有一個細節需要注意:

def foo():
    a = ["x", "y", "z"]
    b = {1, 2, 3}
    c = 3 + 4

print(foo.__code__.co_consts)
"""
(None, ('x', 'y', 'z'), frozenset({1, 2, 3}), 7)
"""

當列表的長度不小于 3 時,里面的元素如果都可以在編譯階段確定,那么整體會作為一個元組被收集起來,這樣多條字節碼可以合并為一條。集合也是類似的,里面的元素整體會作為一個不可變集合被收集起來。

圖片圖片

關于字節碼的更多細節,我們后續再聊。

另外函數里面的變量 c 等于 3 + 4,但常量池里面直接存儲了 7,這個過程叫做常量折疊。常量之間的加減乘除,結果依舊是一個常量,編譯階段就會計算好。

def foo():
    a = 1 + 3
    b = "hello" + " " + "world"
    c = ("a", "b") + ("c", "d")

print(foo.__code__.co_consts)
"""
(None, 4, 'hello world', ('a', 'b', 'c', 'd'))
"""

以上就是常量池,負責保存代碼塊中創建的所有常量。

co_names:符號表,一個元組,保存代碼塊中引用的其它作用域的變量

c = 1

def foo(a, b):
    print(a, b, c)
    d = (list, int, str)

print(foo.__code__.co_names)
"""
('print', 'c', 'list', 'int', 'str')
"""

雖然一切皆對象,但看到的都是指向對象的變量,所以 print, c, list, int, str 都是變量,它們都不在當前 foo 函數的作用域中。

co_exceptiontable:異常處理表

這個字段后續介紹異常處理的時候會細說,目前先有一個簡單的了解即可。當解釋器執行某個指令出現錯誤時,那么會引發一個異常,如果異常產生的位置位于 try 語句塊內,那么解釋器必須跳轉到相應的 except 或 finally 語句塊內,這是顯然的。

在 Python 3.10 以及之前的版本,這個機制是通過引入一個獨立的動態棧,然后跟蹤 try 語句塊實現的。但從 3.11 開始,動態棧被替換成了靜態表,這個表由 co_exceptiontable 字段維護,并在編譯期間就靜態生成了。

def foo():
    try:
        1 / 0
    except Exception:
        pass

print(foo.__code__.co_exceptiontable)
"""
b'\x82\x05\x08\x00\x88\t\x14\x03\x93\x01\x14\x03'
"""

異常處理表本質上是一段字節序列,因為是靜態數據,所以可以高效地讀取。這段字節序列里面包含了代碼塊中的 try / except / finally 信息,當代碼在執行過程中出現異常時,解釋器會查詢這張表,尋找與之匹配的 except 塊。

關于該字段的更多細節,我們后續介紹異常捕獲的時候細說,總之通過將動態棧換成靜態表,可以大幅提升解釋器在異常處理時的效率。

co_flags:函數標識

先來提出一個問題:

def some_func():
    return "hello world"

def some_gen():
    yield
    return "hello world"

print(some_func.__class__)
print(some_gen.__class__)
"""
<class 'function'>
<class 'function'>
"""

print(some_func())
"""
hello world
"""
print(some_gen())
"""
<generator object some_gen at 0x1028a80b0>
"""

調用 some_func 會將代碼執行完畢,調用 some_gen 會返回生成器,但問題是這兩者都是函數類型,為什么執行的時候會有不同的表現呢?

可能有人覺得這還不簡單,Python 具有詞法作用域,由于 some_func 里面沒有出現 yield 關鍵字,所以是普通函數,而 some_gen 里面出現了 yield,所以是生成器函數。

從源代碼來看確實如此,但源代碼是要編譯成 PyCodeObject 對象的,在編譯之后,函數內部是否出現 yield 關鍵字這一信息要怎么體現呢?答案便是通過 co_flags 字段。

然后解釋器內部定義了一系列的標志位,通過和 co_flags 字段按位與,便可判斷函數是否具備指定特征。常見的標志位如下:

// Include/cpython/code.h

// 函數參數是否包含 *args
#define CO_VARARGS      0x0004
// 函數參數是否包含 **kwargs
#define CO_VARKEYWORDS  0x0008
// 函數是否是內層函數
#define CO_NESTED       0x0010
// 函數是否是生成器函數
#define CO_GENERATOR    0x0020
// 函數是否是協程函數
#define CO_COROUTINE            0x0080
// 函數是否是異步生成器函數
#define CO_ASYNC_GENERATOR      0x0200

我們實際測試一下,比如檢測函數的參數類型:

CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008
CO_NESTED = 0x0010


def foo(*args):
    pass

def bar():
    pass

# 因為 foo 的參數包含 *args,所以和 CO_VARARGS 按位與的結果為真
# 而 bar 的參數不包含 *args,所以結果為假
print(foo.__code__.co_flags & CO_VARARGS)  # 4
print(bar.__code__.co_flags & CO_VARARGS)  # 0


def foo(**kwargs):
    pass

def bar():
    pass

print(foo.__code__.co_flags & CO_VARKEYWORDS)  # 8
print(bar.__code__.co_flags & CO_VARKEYWORDS)  # 0


def foo():
    def bar():
        pass
    return bar

# foo 是外層函數,所以和 CO_NESTED 按位與的結果為假
# foo() 返回的是內層函數,所以和 CO_NESTED 按位與的結果為真
print(foo.__code__.co_flags & CO_NESTED)  # 0
print(foo().__code__.co_flags & CO_NESTED)  # 16

當然啦,co_flags 還可以檢測一個函數的類型。比如函數內部出現了 yield,那么它就是一個生成器函數,調用之后可以得到一個生成器;使用 async def 定義,那么它就是一個協程函數,調用之后可以得到一個協程。

這些在詞法分析的時候就可以檢測出來,編譯之后會體現在 co_flags 字段中。

CO_GENERATOR = 0x0020
CO_COROUTINE = 0x0080
CO_ASYNC_GENERATOR = 0x0200

# 如果是生成器函數
# 那么 co_flags & 0x20 為真
def foo1():
    yield
print(foo1.__code__.co_flags & 0x20)  # 32

# 如果是協程函數
# 那么 co_flags & 0x80 為真
async def foo2():
    pass
print(foo2.__code__.co_flags & 0x80)  # 128
# 顯然 foo2 不是生成器函數
# 所以 co_flags & 0x20 為假
print(foo2.__code__.co_flags & 0x20)  # 0

# 如果是異步生成器函數
# 那么 co_flags & 0x200 為真
async def foo3():
    yield
print(foo3.__code__.co_flags & 0x200)  # 512
# 顯然它不是生成器函數、也不是協程函數
# 因此和 0x20、0x80 按位與之后,結果都為假
print(foo3.__code__.co_flags & 0x20)  # 0
print(foo3.__code__.co_flags & 0x80)  # 0

在判斷函數種類時,這種方式是最優雅的。

co_argcount:可以通過位置參數傳遞的參數個數

def foo(a, b, c=3):
    pass
print(foo.__code__.co_argcount)  # 3

def bar(a, b, *args):
    pass
print(bar.__code__.co_argcount)  # 2

def func(a, b, *args, c):
    pass
print(func.__code__.co_argcount)  # 2

函數 foo 中的參數 a、b、c 都可以通過位置參數傳遞,所以結果是 3。而函數 bar 則是兩個,這里不包括 *args。最后函數 func 顯然也是兩個,因為參數 c 只能通過關鍵字參數傳遞。

co_posonlyargcount:只能通過位置參數傳遞的參數個數,Python3.8 新增

def foo(a, b, c):
    pass
print(foo.__code__.co_posonlyargcount)  # 0

def bar(a, b, /, c):
    pass
print(bar.__code__.co_posonlyargcount)  # 2

注意:這里是只能通過位置參數傳遞的參數個數。對于 foo 而言,里面的三個參數既可以通過位置參數、也可以通過關鍵字參數傳遞,所以個數是 0。而函數 bar,里面的 a、b 只能通過位置參數傳遞,所以個數是 2。

co_kwonlyargcount:只能通過關鍵字參數傳遞的參數個數

def foo(a, b=1, c=2, *, d, e):
    pass
print(foo.__code__.co_kwonlyargcount)  # 2

這里是 d 和 e,它們必須通過關鍵字參數傳遞。

co_stacksize:執行該段代碼塊所需要的棧空間

def foo(a, b, c):
    name = "xxx"
    age = 16
    gender = "f"
    c = 33

print(foo.__code__.co_stacksize)  # 1

這個暫時不需要太關注,后續介紹棧幀的時候會詳細說明。

co_firstlineno:代碼塊的起始位置在源文件中的哪一行

def foo(a, b, c):
    pass

# 顯然是文件的第一行
# 或者理解為 def 所在的行
print(foo.__code__.co_firstlineno)  # 1

如果函數出現了調用呢?

def foo():
    return bar

def bar():
    pass

print(foo().__code__.co_firstlineno)  # 4

如果執行 foo,那么會返回函數 bar,因此結果是 def bar(): 所在的行數。所以每個函數都有自己的作用域,以及 PyCodeObject 對象。

_co_cached:結構體的倒數第六個字段,這里需要先拿出來解釋一下,它負責緩存以下字段

// Include/cpython/code.h
typedef struct {
    // 指令集,也就是字節碼,它是一個 bytes 對象 
    PyObject *_co_code;
    // 一個元組,保存當前作用域中創建的局部變量   
    PyObject *_co_varnames;
    // 一個元組,保存外層函數的作用域中被內層函數引用的變量
    PyObject *_co_cellvars;
    // 一個元組,保存內層函數引用的外層函數的作用域中的變量
    PyObject *_co_freevars;
} _PyCoCached;

在之前的版本中,這些字段都是直接單獨定義在 PyCodeObject 中,并且開頭也沒有下劃線。當然啦,如果是通過 Python 獲取的話,那么方式和之前一樣。

def foo(a, b, c):
    name = "satori"
    age = 16
    gender = "f"
    print(name, age, gender)

# 字節碼,一個 bytes 對象,它保存了要操作的指令
# 但光有字節碼是肯定不夠的,還需要其它的靜態信息
# 顯然這些信息連同字節碼一樣,都位于 PyCodeObject 中
print(foo.__code__.co_code)
"""
b'\x97\x00d\x01}\x03d\x02}\x04d\x03}\x05t\x01......'
"""
# 當前作用域中創建的變量,注意它和 co_names 的區別
# co_varnames 保存的是當前作用域中創建的局部變量
# 而 co_names 保存的是當前作用域中引用的其它作用域的變量
print(foo.__code__.co_varnames)
"""
('a', 'b', 'c', 'name', 'age', 'gender')
"""
print(foo.__code__.co_names)
"""
('print',)
"""

然后是 co_cellvars 和 co_freevars,看一下這兩個字段。

def foo(a, b, c):
    def bar():
        print(a, b, c)
    return bar

# co_cellvars:外層函數的作用域中被內層函數引用的變量
# co_freevars:內層函數引用的外層函數的作用域中的變量
print(foo.__code__.co_cellvars)
print(foo.__code__.co_freevars)
"""
('a', 'b', 'c')
()
"""
# foo 里面的變量 a、b、c 被內層函數 bar 引用了
# 所以它的 co_cellvars 是 ('a', 'b', 'c')
# 而 foo 不是內層函數,所以它的 co_freevars 是 ()

bar = foo(1, 2, 3)
print(bar.__code__.co_cellvars)
print(bar.__code__.co_freevars)
"""
()
('a', 'b', 'c')
"""
# bar 引用了外層函數 foo 里面的變量 a、b、c
# 所以它的 co_freevars 是 ('a', 'b', 'c')
# 而 bar 已經是最內層函數了,所以它的 co_cellvars 是 ()

當然目前的函數只嵌套了兩層,但嵌套三層甚至更多層也是一樣的。

def foo(a, b, c):
    def bar(d, e):
        print(a)
        def func():
            print(b, c, d, e)
        return func
    return bar

# 對于 foo 而言,它的內層函數就是 bar,至于最里面的 func
# 由于它定義在 bar 的內部,所以可以看做 bar 函數體的一部分
# 而 foo 里面的變量 a、b、c 都被內層函數引用了
print(foo.__code__.co_cellvars)  # ('a', 'b', 'c')
print(foo.__code__.co_freevars)  # ()

bar = foo(1, 2, 3)
# 對于函數 bar 而言,它的內層函數就是 func
# 而顯然 bar 里面的變量 d 和 e 被 func 引用了
print(bar.__code__.co_cellvars)  # ('d', 'e')
# 然后 bar 引用了外層函數 foo 里面的 a、b、c
print(bar.__code__.co_freevars)  # ('a', 'b', 'c')
# 所以 co_cellvars 和 co_freevars 這兩個字段的關系有點類似鏡像

co_cellvars 和 co_freevars 在后續介紹閉包的時候會用到,以上就是這幾個字段的含義。

co_nlocals:代碼塊中局部變量的個數,也包括參數

def foo(a, b, *args, c, **kwargs):
    name = "xxx"
    age = 16
    gender = "f"
    c = 33

print(foo.__code__.co_varnames)
"""
('a', 'b', 'c', 'args', 'kwargs', 'name', 'age', 'gender')
"""
print(foo.__code__.co_nlocals)
"""
8
"""

co_varnames 保存的是代碼塊的局部變量,顯然 co_nlocals 就是它的長度。并且我們看到在編譯之后,函數的局部變量就已經確定了,因為它們是靜態存儲的。

co_ncellvars:cell 變量的個數,即 co_cellvars 的長度

該字段解釋器沒有暴露出來。

co_nfreevars:free 變量的個數,即 co_freevars 的長度

該字段解釋器沒有暴露出來。

co_nlocalsplus:局部變量、cell 變量、free 變量的個數之和

該字段解釋器沒有暴露出來。

co_framesize:棧幀的大小

解釋器在將源代碼編譯成 PyCodeObject 之后,還要在此之上繼續創建 PyFrameObject 對象,即棧幀對象。也就是說,字節碼是在棧幀中被執行的,棧幀是虛擬機執行的上下文,局部變量、臨時變量、以及函數執行的相關信息都保存在棧幀中。

當然該字段解釋器也沒有暴露出來,我們后續會詳細討論它。

co_localsplusnames:一個元組,包含局部變量、cell 變量、free 變量,當然嚴謹的說法應該是變量的名稱

而上面的 co_nlocalsplus 字段便是 co_localsplusnames 的長度。

  • co_varnames:保存所有的局部變量;co_nlocals:局部變量的個數。
  • co_cellvars:保存所有的 cell 變量;co_ncellvars:cell 變量的個數;
  • co_freevars:保存所有的 free 變量;co_nfreevars:free 變量的個數;

所以可以得出如下結論:

圖片圖片

這個字段很重要,之后會反復用到。

co_localspluskinds:標識 co_localsplusnames 里面的每個變量的種類

我們說了,co_localsplusnames 里面包含了局部變量、cell 變量、free 變量的名稱,它們整體是作為一個元組存儲的。那么問題來了,當從 co_localsplusnames 里面獲取一個變量時,解釋器怎么知道這個變量是局部變量,還是 cell 變量或者 free 變量呢?

所以便有了 co_localspluskinds 字段,它是一段字節序列,一個字節對應一個變量。

// Include/internal/pycore_code.h
#define CO_FAST_HIDDEN  0x10  
#define CO_FAST_LOCAL   0x20  // 局部變量
#define CO_FAST_CELL    0x40  // cell 變量
#define CO_FAST_FREE    0x80  // free 變量

比如 co_localspluskinds[3] 等于 0x20,那么 co_localsplusnames[3] 對應的便是局部變量。這里可能有人好奇,CO_FAST_HIDDEN 表示的是啥?顧名思義,該宏對應的是隱藏變量,所謂隱藏變量指的就是那些在當前作用域中不可見的變量。

def foo():
    lst = [x for x in range(10)]

比如列表推導式里面的循環變量,它就是一個隱藏變量,生命周期只局限于列表解析式內部,不會泄露到當前的局部作用域中。但 Python2 是會泄露的,如果你還要維護 Python2 老項目的話,那么這里要多加注意。

圖片圖片

以上就是 co_localspluskinds 字段的作用。

co_filename:代碼塊所在的文件的路徑

# 文件名:main.py
def foo():
    pass


print(foo.__code__.co_filename)
"""
/Users/satori/Documents/testing_project/main.py
"""

如果你無法使用 IDE,那么便可通過該字段查看函數定義在哪個文件中。

co_name:代碼塊的名字

def foo():
    pass

print(foo.__code__.co_name)  # foo

對于函數來說,代碼塊的名字就是函數名。

co_qualname:代碼塊的全限定名

def foo():
    pass

class A:
    def foo(self):
        pass

print(foo.__code__.co_qualname)  # foo
print(A.foo.__code__.co_qualname)  # A.foo

# 如果是獲取 co_name 字段,那么打印的則都是 "foo"

如果是類的成員函數,那么會將類名一起返回。

co_linetable:存儲指令和源代碼行號之間的對應關系

PyCodeObject 是源代碼編譯之后的產物,雖然兩者的結構千差萬別,但體現出的信息是一致的。像源代碼具有行號,那么編譯成 PyCodeObject 之后,行號信息也應該要有專門的字段來維護,否則報錯時我們就無法快速定位到行號。

在 3.10 之前,行號信息由 co_lnotab 字段(一個字節序列)維護,并且保存的是增量信息,舉個例子。

def foo():
    name = "古明地覺"
    hobby = [
        "sing",
        "dance",
        "rap",
        "??"
    ]
    age = 16

我們通過 dis 模塊反編譯一下。

圖片圖片

第一列數字表示行號,第二列數字表示字節碼指令的偏移量,或者說指令在整個字節碼指令集中的索引。我們知道字節碼指令集就是一段字節序列,由 co_code 字段維護,并且每個指令都帶有一個參數,所以偏移量(索引)為 0 2 4 6 8 ··· 的字節便是指令,偏移量為 1 3 5 7 9 ··· 的字節表示參數。

關于反編譯的具體細節后續會說,總之一個字節碼指令就是一個八位整數。對于當前函數來說,它的字節碼偏移量和行號的對應關系如下:

圖片圖片

當偏移量為 0 時,證明還沒有進入到函數體,那么源代碼行號便是 def 關鍵字所在的行號。然后偏移量增加 2、行號增加 1,接著偏移量增加 4、行號增加 1、最后偏移量增加 8、行號增加 6。

那么 co_lnotab 便是 2 1 4 1 8 6,我們測試一下。

結果和我們分析的一樣,但 co_lnotab 字段是 3.10 之前的,現在已經被替換成了 co_linetable,并且包含了更多的信息。當然啦,在 Python 里面這兩個字段都是可以訪問的,盡管有一部分字段已經被移除了,但為了保證兼容性,底層依舊支持我們通過 Python 訪問。

co_weakreflist:弱引用列表

PyCodeObject 對象支持弱引用,弱引用它的 PyObject * 會保存在該列表中。

以上就是 PyCodeObject 里面的字段的含義,至于剩下的幾個字段目前先跳過,后續涉及到的時候再說。

圖片圖片

小結

  • Python 解釋器 = Python 編譯器 + Python 虛擬機。
  • 編譯器先將 .py 源碼文件編譯成 PyCodeObject 對象,然后再交給虛擬機執行。
  • PyCodeObject 對象可以認為是源碼文件的另一種等價形式,但經過編譯,虛擬機可以更快速地執行。
  • 為了避免每次都要對源文件進行編譯,因此編譯后的結果會序列化在 .pyc 文件中,如果源文件沒有做改動,那么下一次執行時會直接從 .pyc 中讀取。
  • Python 的函數、類、模塊等,都具有各自的作用域,每個作用域對應一個獨立的代碼塊,在編譯時,Python 編譯器會為每個代碼塊都創建一個 PyCodeObject 對象。

最后我們又詳細介紹了 PyCodeObject 里面的字段的含義,相比幾年前剖析的 Python3.8 版本的源碼,3.12 的改動還是比較大的,底層增加了不少字段,并且移除了部分字段。但對于 Python 使用者而言,還是和之前一樣,解釋器依舊將它們以 <class 'code'> 實例屬性的形式暴露了出來。

責任編輯:武曉燕 來源: 古明地覺的編程教室
相關推薦

2024-10-28 12:06:09

2024-05-22 08:02:30

2009-09-08 18:02:20

CCNA用途

2015-09-18 13:08:36

更新RedstoneWindows 10

2020-04-21 12:09:47

JVM消化字節碼

2024-05-07 09:24:12

Python源碼Java

2024-11-25 12:20:00

Hystrix微服務架構

2020-08-10 15:48:01

Python輪子計算

2024-08-08 11:05:22

2022-02-24 23:37:19

區塊鏈錢包比特幣

2023-04-17 14:21:19

5G無線技術

2022-08-08 07:04:34

URLIPHTTP

2023-05-04 00:16:39

數字化轉型運營

2024-12-03 09:34:35

觀察者模 式編程Javav

2013-05-31 09:17:31

云計算云技術大數據

2024-07-30 14:01:51

Java字節碼JVM?

2018-03-22 14:47:13

容器開發人員筆記本

2023-11-07 08:00:00

Kubernetes

2022-08-26 16:32:08

云計算公有云私有云

2021-05-25 07:59:59

Linux運維Linux系統
點贊
收藏

51CTO技術棧公眾號

暗呦丨小u女国产精品| 麻豆三级在线观看| 欧美孕妇孕交| 免费成人在线观看| 久久中文字幕在线| 中文字幕a在线观看| 日本成人片在线| 亚洲免费观看高清| 欧美系列一区| 国产超碰人人模人人爽人人添| 日韩午夜精品| 久久精品一本久久99精品| 老司机午夜免费福利| 欧美色999| 夜夜精品浪潮av一区二区三区| 免费久久99精品国产自| 国产三级三级在线观看| 99亚洲一区二区| 精品国产一区久久久| 在线 丝袜 欧美 日韩 制服| 欧美电影院免费观看| 色欧美片视频在线观看| 69sex久久精品国产麻豆| 日韩成人影视| 久久久久青草大香线综合精品| 超碰97人人人人人蜜桃| 一级特黄aaa大片在线观看| 国产一区二区三区久久| 欧美成人手机在线| 手机免费观看av| 九九亚洲视频| 日韩精品福利网站| 国产成人美女视频| 日韩成人亚洲| 欧美视频在线免费| 日韩网站在线免费观看| 色女人在线视频| 亚洲欧美成aⅴ人在线观看| 日韩欧美精品久久| 美女做暖暖视频免费在线观看全部网址91| 国产高清视频一区| 91欧美激情另类亚洲| 一区二区视频免费观看| 久久综合五月| 琪琪亚洲精品午夜在线| 日产精品久久久久| 欧美日一区二区在线观看| 久久精品视频免费播放| 一区二区精品免费| 日韩最新在线| 日韩av在线网址| 欧美xxxx×黑人性爽| 国产精品香蕉| 亚洲国模精品私拍| 国产精品无码专区| 亚洲另类av| 国产丝袜精品第一页| 亚洲欧美色图视频| 要久久爱电视剧全集完整观看| 亚洲精品国产精品国自产在线 | 久久男女视频| 日韩美女视频中文字幕| 丁香社区五月天| 视频一区视频二区中文字幕| 国产精品久久久久久久久久三级| 蜜臀99久久精品久久久久小说| 日韩精品免费视频人成| 国产精品免费看久久久香蕉 | 久久精品国产99久久6| 国产精品中文在线| 国产精品久久777777换脸| 国产自产v一区二区三区c| 91九色国产视频| 成人av无码一区二区三区| 成人免费黄色大片| 精品无码久久久久久久动漫| 欧美高清电影在线| 欧美国产精品中文字幕| avove在线观看| 爱看av在线| 色综合天天狠狠| 污污网站免费观看| 91国内精品| 亚洲欧洲在线看| 波多野结衣喷潮| 精品999日本| 国产成一区二区| 99产精品成人啪免费网站| 国产成人啪免费观看软件| 久久久久久久免费| 无遮挡的视频在线观看| 亚洲一线二线三线久久久| 免费av网址在线| 成人黄色91| 精品中文字幕久久久久久| 91免费在线看片| 亚洲精选91| 国产一区在线播放| 欧美一级特黄aaaaaa| 日本一二三不卡| 亚洲色成人www永久在线观看| 自拍偷自拍亚洲精品被多人伦好爽| 91精品久久久久久久91蜜桃| 中国黄色a级片| 欧美不卡高清| 国产精品久久久久久久天堂| 亚洲精品911| 亚洲国产高清在线观看视频| 天天夜碰日日摸日日澡性色av| 欧美成人黄色| 日韩精品一二三四区| 日本a级片视频| 日本中文一区二区三区| 国产一区二区精品免费| 国产精品剧情| 欧美亚洲综合在线| 午夜视频在线观看国产| 亚洲一区二区日韩| 国产精品午夜一区二区欲梦| 污污网站在线免费观看| 亚洲美女免费视频| 日本在线观看免费视频| 日韩精品免费一区二区三区竹菊| 久久视频在线观看免费| 中文字幕二区三区| 久久综合国产精品| 天堂8在线天堂资源bt| 欧美jizz18| 国产亚洲一区精品| 国产一区二区视频免费| 成人看片黄a免费看在线| 一级一片免费播放| jizz欧美| 色老头一区二区三区在线观看| 国产精品一区无码| 97se亚洲国产综合自在线不卡| 隔壁人妻偷人bd中字| 欧美2区3区4区| 欧美成人自拍视频| www.看毛片| 一区二区三区在线免费| 少妇性l交大片7724com| 亚洲国产一区二区三区在线播放| 成人福利视频网| 好吊日视频在线观看| 91精品国产欧美日韩| 欧美日韩黄色网| 国产主播一区二区三区| 色哟哟免费网站| 6080成人| 91大神福利视频在线| 五月婷婷六月色| 欧美午夜激情视频| 免费黄在线观看| 美女视频黄久久| 在线一区亚洲| 中文字幕一区二区三区中文字幕| 色综合久久悠悠| 丰满少妇一级片| 欧美日韩免费区域视频在线观看| 国产熟妇搡bbbb搡bbbb| 丝袜脚交一区二区| 亚洲制服欧美久久| 国产精品xnxxcom| 欧美激情二区三区| 性插视频在线观看| 91黄色免费看| 91n在线视频| 国产成人亚洲综合a∨婷婷图片| 激情六月天婷婷| 欧美电影免费网站| 国产精品99一区| 欧美69xxxx| 精品福利一区二区三区 | av在线网址观看| 欧美一级搡bbbb搡bbbb| 国产大片中文字幕| 久久蜜桃av一区二区天堂| 五月婷婷六月丁香激情| 欧美精品啪啪| 美女被啪啪一区二区| 欧美97人人模人人爽人人喊视频| 九九热99久久久国产盗摄| 亚洲aⅴ在线观看| 欧美日韩免费在线视频| 久久久无码精品亚洲国产| 91视频精品在这里| 911福利视频| 亚洲欧美日本日韩| 亚洲免费视频播放| 台湾佬综合网| 成人午夜小视频| 中文不卡1区2区3区| 久久精品99国产精品酒店日本| 国产91绿帽单男绿奴| 在线观看视频欧美| 久久久久久久久艹| 国产欧美一区二区在线| 久久发布国产伦子伦精品| 麻豆久久精品| 黄色一级大片免费| 日韩片欧美片| 久久国产主播精品| 精品国产乱码一区二区三区| 国产成人福利网站| 国产丝袜精品丝袜| 久久精品国产69国产精品亚洲 | 久久新电视剧免费观看| 男女视频在线观看网站| 丝袜美腿成人在线| 奇米精品一区二区三区| 伊人久久大香线蕉综合四虎小说| 日本午夜精品一区二区| 国产精品毛片视频| 91沈先生作品| 国产成人免费精品| 青青久久av北条麻妃黑人| 青青在线视频| 欧美成人亚洲成人日韩成人| 亚洲免费视频一区二区三区| 亚洲天堂免费观看| 午夜福利视频一区二区| 精品国产伦理网| 国产欧美一级片| 欧美日韩一区二区三区高清| 国产午夜麻豆影院在线观看| 午夜精品福利在线| 久久久久久久久久久久久久免费看| 国产精品久久影院| 色欲狠狠躁天天躁无码中文字幕| 97久久久精品综合88久久| 精品人妻在线视频| 懂色av一区二区在线播放| 91网址在线观看精品| 久久国产精品99久久人人澡| 免费看污污网站| 三级精品在线观看| 无遮挡又爽又刺激的视频| 亚洲免费网站| 成人综合视频在线| 性高湖久久久久久久久| 男人用嘴添女人下身免费视频| 在线成人av| 青青青青草视频| 夜夜嗨网站十八久久| 妞干网在线视频观看| 亚洲乱码视频| 国产在线观看福利| 久久婷婷久久| 91色国产在线| 捆绑紧缚一区二区三区视频| 亚洲精品性视频| 欧美aⅴ一区二区三区视频| 在线观看国产中文字幕| 久久99久久久欧美国产| 亚洲精品国产一区二区三区| 狠狠色丁香婷婷综合久久片| 国产高清av片| 国产91丝袜在线播放| 手机免费看av片| 久久伊人中文字幕| 欧美成人另类视频| 成人欧美一区二区三区小说| 印度午夜性春猛xxx交| 亚洲午夜电影网| 国产原创视频在线| 欧美午夜影院一区| 国产富婆一级全黄大片| 亚洲国产精品久久久久| 国产在线观看网站| 久久视频精品在线| 三妻四妾的电影电视剧在线观看| 全亚洲最色的网站在线观看| 99只有精品| 亚洲综合中文字幕在线观看| 欧美1区2区3区4区| 亚洲国产欧美一区二区三区不卡| 亚欧美无遮挡hd高清在线视频| 丰满人妻一区二区三区53号| 欧美一级二区| 男女污污视频网站| av不卡在线播放| 国产三级精品三级观看| 亚洲在线一区二区三区| 成人免费毛片男人用品| 88在线观看91蜜桃国自产| 理论片中文字幕| 中文字幕国产精品久久| xxx.xxx欧美| 国产噜噜噜噜噜久久久久久久久| av日韩精品| 亚洲永久激情精品| av成人国产| 国产探花一区二区三区| 久久精品视频在线免费观看| 日韩a级片在线观看| 色综合久久88色综合天天6| 国产深喉视频一区二区| 亚洲男子天堂网| 日韩伦理电影网站| 国产精品入口免费视频一| 9l视频自拍蝌蚪9l视频成人| 亚洲一区二区在线观| 亚洲精品在线二区| 无套白嫩进入乌克兰美女| 久久久精品综合| 日韩av黄色片| 欧美一区二区三区婷婷月色 | 欧美福利小视频| 日韩黄色在线| 日本精品一区二区三区高清 久久| 欧美激情第8页| 亚洲欧美日本一区二区三区| 国产日韩欧美精品综合| www.天天色| 欧美tickling网站挠脚心| 激情视频在线观看| 国产精品第七十二页| 日韩高清成人在线| 97超碰在线人人| 国产成人av电影| 四虎884aa成人精品| 欧美日韩一级二级三级| 国产一区二区影视| 欧美又大又硬又粗bbbbb| 99re8这里有精品热视频免费| 国产大尺度在线观看| 久久66热偷产精品| 欧美自拍偷拍网| 欧美在线观看18| 户外极限露出调教在线视频| 97热在线精品视频在线观看| 999国产精品一区| 国产成a人亚洲精v品在线观看| 国产精品自拍在线| 性色av无码久久一区二区三区| 欧美日韩一区二区在线视频| avav免费在线观看| 国产精品亚洲激情| 日韩av密桃| 天天干天天玩天天操| 国产精品久久久久四虎| 国产又粗又猛又黄| 色av吧综合网| www.成人在线.com| 女人床在线观看| 成人亚洲一区二区一| 日本一级黄色录像| 精品亚洲va在线va天堂资源站| 看黄在线观看| 欧美精品欧美精品系列c| 老司机精品视频网站| 国产综合精品久久久久成人av| 欧美亚洲一区三区| 欧美尤物美女在线| 亚洲精品免费av| 激情成人综合| 亚洲永久无码7777kkk| 色综合久久99| 888av在线| www久久99| 香蕉久久夜色精品| 久久视频一区二区三区| 欧美一区二区三区在线看 | 国产成人91久久精品| 色综合蜜月久久综合网| 天天操精品视频| 亚洲国产aⅴ成人精品无吗| 日本一二三区在线视频| 国产精品96久久久久久又黄又硬| 久久中文字幕av| 久久无码专区国产精品s| 狠狠爱在线视频一区| 国产1区2区3区在线| 亚洲最大成人网色| 国产午夜精品一区二区三区欧美 | 不卡一区综合视频| 992kp免费看片| 图片区小说区国产精品视频| 成年午夜在线| 不卡一区二区三区视频| 葵司免费一区二区三区四区五区| 日本在线观看网址| 亚洲精品一区二区三区福利| 成人在线视频播放| 日韩a级黄色片| 91丨九色丨黑人外教| 一级黄色大片免费观看| 久久久爽爽爽美女图片| 欧美日一区二区| 少妇熟女视频一区二区三区| 91激情在线视频| 黄页在线观看免费| 性刺激综合网| caoporm超碰国产精品| 一级特黄aaaaaa大片| 欧美在线视频a| 狠狠干成人综合网| 亚洲激情图片网| 日韩大片免费观看视频播放|