亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python字節碼與程序執行過程是什么

發布時間:2022-05-17 15:11:17 來源:億速云 閱讀:103 作者:iii 欄目:開發技術

今天小編給大家分享一下Python字節碼與程序執行過程是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

問題:

我們每天都要編寫一些Python程序,或者用來處理一些文本,或者是做一些系統管理工作。程序寫好后,只需要敲下python命令,便可將程序啟動起來并開始執行:

$ python some-program.py

那么,一個文本形式的.py文件,是如何一步步轉換為能夠被CPU執行的機器指令的呢?此外,程序執行過程中可能會有.pyc文件生成,這些文件又有什么作用呢?

1. 執行過程

雖然從行為上看Python更像Shell腳本這樣的解釋性語言,但實際上Python程序執行原理本質上跟Java或者C#一樣,都可以歸納為虛擬機字節碼。Python執行程序分為兩步:先將程序代碼編譯成字節碼,然后啟動虛擬機執行字節碼:

Python字節碼與程序執行過程是什么

雖然Python命令也叫做Python解釋器,但跟其他腳本語言解釋器有本質區別。實際上,Python解釋器包含編譯器以及虛擬機兩部分。當Python解釋器啟動后,主要執行以下兩個步驟:

編譯器將.py文件中的Python源碼編譯成字節碼虛擬機逐行執行編譯器生成的字節碼

因此,.py文件中的Python語句并沒有直接轉換成機器指令,而是轉換成Python字節碼。

2. 字節碼

Python程序的編譯結果是字節碼,里面有很多關于Python運行的相關內容。因此,不管是為了更深入理解Python虛擬機運行機制,還是為了調優Python程序運行效率,字節碼都是關鍵內容。那么,Python字節碼到底長啥樣呢?我們如何才能獲得一個Python程序的字節碼呢——Python提供了一個內置函數compile用于即時編譯源碼。我們只需將待編譯源碼作為參數調用compile函數,即可獲得源碼的編譯結果。

3. 源碼編譯

下面,我們通過compile函數來編譯一個程序:

源碼保存在demo.py文件中:

PI = 3.14

def circle_area(r):
    return PI * r ** 2

class Person(object):
    def __init__(self, name):
        self.name = name

    def say(self):
        print('i am', self.name)

編譯之前需要將源碼從文件中讀取出來:

>>> text = open('D:\myspace\code\pythonCode\mix\demo.py').read()
>>> print(text)
PI = 3.14

def circle_area(r):
    return PI * r ** 2

class Person(object):
    def __init__(self, name):
        self.name = name

    def say(self):
        print('i am', self.name)

然后調用compile函數來編譯源碼:

>>> result = compile(text,'D:\myspace\code\pythonCode\mix\demo.py', 'exec')

compile函數必填的參數有3個:

source:待編譯源碼

filename:源碼所在文件名

mode:編譯模式,exec表示將源碼當作一個模塊來編譯

三種編譯模式:

exec:用于編譯模塊源碼

single:用于編譯一個單獨的Python語句(交互式下)

eval:用于編譯一個eval表達式

4. PyCodeObject

通過compile函數,我們獲得了最后的源碼編譯結果result:

>>> result
<code object <module> at 0x000001DEC2FCF680, file "D:\myspace\code\pythonCode\mix\demo.py", line 1>
>>> result.__class__
<class 'code'>

最終我們得到了一個code類型的對象,它對應的底層結構體是PyCodeObject

PyCodeObject源碼如下:

/* Bytecode object */
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_linetable;     /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

代碼對象PyCodeObject用于存儲編譯結果,包括字節碼以及代碼涉及的常量、名字等等。關鍵字段包括:

字段用途
co_argcount參數個數
co_kwonlyargcount關鍵字參數個數
co_nlocals局部變量個數
co_stacksize執行代碼所需棧空間
co_flags標識
co_firstlineno代碼塊首行行號
co_code指令操作碼,即字節碼
co_consts常量列表
co_names名字列表
co_varnames局部變量名列表

下面打印看一下這些字段對應的數據:

通過co_code字段獲得字節碼:

>>> result.co_code
b'd\x00Z\x00d\x01d\x02\x84\x00Z\x01G\x00d\x03d\x04\x84\x00d\x04e\x02\x83\x03Z\x03d\x05S\x00'

通過co_names字段獲得代碼對象涉及的所有名字:

>>> result.co_names
('PI', 'circle_area', 'object', 'Person')

通過co_consts字段獲得代碼對象涉及的所有常量:

>>> result.co_consts
(3.14, <code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>, 'circle_area', <code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>, 'Person', None)

可以看到,常量列表中還有兩個代碼對象,其中一個是circle_area函數體,另一個是Person類定義體。對應Python中作用域的劃分方式,可以自然聯想到:每個作用域對應一個代碼對象。如果這個假設成立,那么Person代碼對象的常量列表中應該還包括兩個代碼對象:init函數體和say函數體。下面取出Person類代碼對象來看一下:

>>> person_code = result.co_consts[3]
>>> person_code
<code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>
>>> person_code.co_consts
('Person', <code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>, 'Person.__init__', <code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>, 'Person.say', None)

因此,我們得出結論:Python源碼編譯后,每個作用域都對應著一個代碼對象,子作用域代碼對象位于父作用域代碼對象的常量列表里,層級一一對應。

Python字節碼與程序執行過程是什么

至此,我們對Python源碼的編譯結果&mdash;&mdash;代碼對象PyCodeObject有了最基本的認識,后續會在虛擬機、函數機制、類機制中進一步學習。

5. 反編譯

字節碼是一串不可讀的字節序列,跟二進制機器碼一樣。如果想讀懂機器碼,可以將其反匯編,那么字節碼可以反編譯嗎?

通過dis模塊可以將字節碼反編譯:

>>> import dis
>>> dis.dis(result.co_code)
 0 LOAD_CONST               0 (0)
 2 STORE_NAME               0 (0)
 4 LOAD_CONST               1 (1)
 6 LOAD_CONST               2 (2)
 8 MAKE_FUNCTION            0
10 STORE_NAME               1 (1)
12 LOAD_BUILD_CLASS
14 LOAD_CONST               3 (3)
16 LOAD_CONST               4 (4)
18 MAKE_FUNCTION            0
20 LOAD_CONST               4 (4)
22 LOAD_NAME                2 (2)
24 CALL_FUNCTION            3
26 STORE_NAME               3 (3)
28 LOAD_CONST               5 (5)
30 RETURN_VALUE

字節碼反編譯后的結果和匯編語言很類似。其中,第一列是字節碼的偏移量,第二列是指令,第三列是操作數。以第一條字節碼為例,LOAD_CONST指令將常量加載進棧,常量下標由操作數給出,而下標為0的常量是:

>>> result.co_consts[0]3.14

這樣,第一條字節碼的意義就明確了:將常量3.14加載到棧。

由于代碼對象保存了字節碼、常量、名字等上下文信息,因此直接對代碼對象進行反編譯可以得到更清晰的結果:

>>>dis.dis(result)
  1           0 LOAD_CONST               0 (3.14)
              2 STORE_NAME               0 (PI)

  3           4 LOAD_CONST               1 (<code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>)
              6 LOAD_CONST               2 ('circle_area')
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (circle_area)

  6          12 LOAD_BUILD_CLASS
             14 LOAD_CONST               3 (<code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>)
             16 LOAD_CONST               4 ('Person')
             18 MAKE_FUNCTION            0
             20 LOAD_CONST               4 ('Person')
             22 LOAD_NAME                2 (object)
             24 CALL_FUNCTION            3
             26 STORE_NAME               3 (Person)
             28 LOAD_CONST               5 (None)
             30 RETURN_VALUE

Disassembly of <code object circle_area at 0x0000023D04D3F310, file "D:\myspace\code\pythonCode\mix\demo.py", line 3>:
  4           0 LOAD_GLOBAL              0 (PI)
              2 LOAD_FAST                0 (r)
              4 LOAD_CONST               1 (2)
              6 BINARY_POWER
              8 BINARY_MULTIPLY
             10 RETURN_VALUE

Disassembly of <code object Person at 0x0000023D04D3F5D0, file "D:\myspace\code\pythonCode\mix\demo.py", line 6>:
  6           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('Person')
              6 STORE_NAME               2 (__qualname__)

  7           8 LOAD_CONST               1 (<code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>)
             10 LOAD_CONST               2 ('Person.__init__')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               3 (__init__)

 10          16 LOAD_CONST               3 (<code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>)
             18 LOAD_CONST               4 ('Person.say')
             20 MAKE_FUNCTION            0
             22 STORE_NAME               4 (say)
             24 LOAD_CONST               5 (None)
             26 RETURN_VALUE

Disassembly of <code object __init__ at 0x0000023D04D3F470, file "D:\myspace\code\pythonCode\mix\demo.py", line 7>:
  8           0 LOAD_FAST                1 (name)
              2 LOAD_FAST                0 (self)
              4 STORE_ATTR               0 (name)
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

Disassembly of <code object say at 0x0000023D04D3F520, file "D:\myspace\code\pythonCode\mix\demo.py", line 10>:
 11           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('i am')
              4 LOAD_FAST                0 (self)
              6 LOAD_ATTR                1 (name)
              8 CALL_FUNCTION            2
             10 POP_TOP
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

操作數指定的常量或名字的實際值在旁邊的括號內列出,此外,字節碼以語句為單位進行了分組,中間以空行隔開,語句的行號在字節碼前面給出。例如PI = 3.14這個語句就被會變成了兩條字節碼:

  1           0 LOAD_CONST               0 (3.14)
              2 STORE_NAME               0 (PI)

6. pyc

如果將demo作為模塊導入,Python將在demo.py文件所在目錄下生成.pyc文件:

>>> import demo

Python字節碼與程序執行過程是什么

pyc文件會保存經過序列化處理的代碼對象PyCodeObject。這樣一來,Python后續導入demo模塊時,直接讀取pyc文件并反序列化即可得到代碼對象,避免了重復編譯導致的開銷。只有demo.py有新修改(時間戳比.pyc文件新),Python才會重新編譯。

因此,對比Java而言:Python中的.py文件可以類比Java中的.java文件,都是源碼文件;而.pyc文件可以類比.class文件,都是編譯結果。只不過Java程序需要先用編譯器javac命令來編譯,再用虛擬機java命令來執行;而Python解釋器把這兩個過程都完成了。

以上就是“Python字節碼與程序執行過程是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

奈曼旗| 稷山县| 枣阳市| 呼玛县| 宜川县| 江阴市| 如皋市| 金寨县| 沈丘县| 马山县| 潜江市| 阳谷县| 平谷区| 江达县| 西贡区| 德州市| 北海市| 邛崃市| 哈密市| 金川县| 桂平市| 汕尾市| 紫阳县| 株洲县| 合肥市| 海伦市| 阿拉善右旗| 永嘉县| 山东| 比如县| 汶上县| 江山市| 长阳| 临澧县| 东光县| 马尔康县| 南川市| 巴林右旗| 靖边县| 江阴市| 安平县|