欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python字節(jié)碼與程序執(zhí)行過(guò)程詳解

 更新時(shí)間:2022年05月17日 14:03:08   作者:Blanker  
這篇文章主要為大家介紹了Python字節(jié)碼與程序執(zhí)行過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

問(wèn)題:

我們每天都要編寫一些Python程序,或者用來(lái)處理一些文本,或者是做一些系統(tǒng)管理工作。程序?qū)懞煤螅恍枰孟聀ython命令,便可將程序啟動(dòng)起來(lái)并開(kāi)始執(zhí)行:

$ python some-program.py

那么,一個(gè)文本形式的.py文件,是如何一步步轉(zhuǎn)換為能夠被CPU執(zhí)行的機(jī)器指令的呢?此外,程序執(zhí)行過(guò)程中可能會(huì)有.pyc文件生成,這些文件又有什么作用呢?

1. 執(zhí)行過(guò)程

雖然從行為上看Python更像Shell腳本這樣的解釋性語(yǔ)言,但實(shí)際上Python程序執(zhí)行原理本質(zhì)上跟Java或者C#一樣,都可以歸納為虛擬機(jī)和字節(jié)碼。Python執(zhí)行程序分為兩步:先將程序代碼編譯成字節(jié)碼,然后啟動(dòng)虛擬機(jī)執(zhí)行字節(jié)碼:

雖然Python命令也叫做Python解釋器,但跟其他腳本語(yǔ)言解釋器有本質(zhì)區(qū)別。實(shí)際上,Python解釋器包含編譯器以及虛擬機(jī)兩部分。當(dāng)Python解釋器啟動(dòng)后,主要執(zhí)行以下兩個(gè)步驟:

編譯器將.py文件中的Python源碼編譯成字節(jié)碼虛擬機(jī)逐行執(zhí)行編譯器生成的字節(jié)碼

因此,.py文件中的Python語(yǔ)句并沒(méi)有直接轉(zhuǎn)換成機(jī)器指令,而是轉(zhuǎn)換成Python字節(jié)碼。

2. 字節(jié)碼

Python程序的編譯結(jié)果是字節(jié)碼,里面有很多關(guān)于Python運(yùn)行的相關(guān)內(nèi)容。因此,不管是為了更深入理解Python虛擬機(jī)運(yùn)行機(jī)制,還是為了調(diào)優(yōu)Python程序運(yùn)行效率,字節(jié)碼都是關(guān)鍵內(nèi)容。

那么,Python字節(jié)碼到底長(zhǎng)啥樣呢?我們?nèi)绾尾拍塬@得一個(gè)Python程序的字節(jié)碼呢——Python提供了一個(gè)內(nèi)置函數(shù)compile用于即時(shí)編譯源碼。我們只需將待編譯源碼作為參數(shù)調(diào)用compile函數(shù),即可獲得源碼的編譯結(jié)果。

3. 源碼編譯

下面,我們通過(guò)compile函數(shù)來(lái)編譯一個(gè)程序:

源碼保存在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)

編譯之前需要將源碼從文件中讀取出來(lái):

>>> 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)

然后調(diào)用compile函數(shù)來(lái)編譯源碼:

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

compile函數(shù)必填的參數(shù)有3個(gè):

source:待編譯源碼

filename:源碼所在文件名

mode:編譯模式,exec表示將源碼當(dāng)作一個(gè)模塊來(lái)編譯

三種編譯模式:

exec:用于編譯模塊源碼

single:用于編譯一個(gè)單獨(dú)的Python語(yǔ)句(交互式下)

eval:用于編譯一個(gè)eval表達(dá)式

4. PyCodeObject

通過(guò)compile函數(shù),我們獲得了最后的源碼編譯結(jié)果result:

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

最終我們得到了一個(gè)code類型的對(duì)象,它對(duì)應(yīng)的底層結(jié)構(gòu)體是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.
};

代碼對(duì)象PyCodeObject用于存儲(chǔ)編譯結(jié)果,包括字節(jié)碼以及代碼涉及的常量、名字等等。關(guān)鍵字段包括:

字段用途
co_argcount參數(shù)個(gè)數(shù)
co_kwonlyargcount關(guān)鍵字參數(shù)個(gè)數(shù)
co_nlocals局部變量個(gè)數(shù)
co_stacksize執(zhí)行代碼所需棧空間
co_flags標(biāo)識(shí)
co_firstlineno代碼塊首行行號(hào)
co_code指令操作碼,即字節(jié)碼
co_consts常量列表
co_names名字列表
co_varnames局部變量名列表

下面打印看一下這些字段對(duì)應(yīng)的數(shù)據(jù):

通過(guò)co_code字段獲得字節(jié)碼:

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

通過(guò)co_names字段獲得代碼對(duì)象涉及的所有名字:

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

通過(guò)co_consts字段獲得代碼對(duì)象涉及的所有常量:

>>> 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)

可以看到,常量列表中還有兩個(gè)代碼對(duì)象,其中一個(gè)是circle_area函數(shù)體,另一個(gè)是Person類定義體。對(duì)應(yīng)Python中作用域的劃分方式,可以自然聯(lián)想到:每個(gè)作用域?qū)?yīng)一個(gè)代碼對(duì)象。如果這個(gè)假設(shè)成立,那么Person代碼對(duì)象的常量列表中應(yīng)該還包括兩個(gè)代碼對(duì)象:init函數(shù)體和say函數(shù)體。下面取出Person類代碼對(duì)象來(lái)看一下:

>>> 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)

因此,我們得出結(jié)論:Python源碼編譯后,每個(gè)作用域都對(duì)應(yīng)著一個(gè)代碼對(duì)象,子作用域代碼對(duì)象位于父作用域代碼對(duì)象的常量列表里,層級(jí)一一對(duì)應(yīng)。

至此,我們對(duì)Python源碼的編譯結(jié)果——代碼對(duì)象PyCodeObject有了最基本的認(rèn)識(shí),后續(xù)會(huì)在虛擬機(jī)、函數(shù)機(jī)制、類機(jī)制中進(jìn)一步學(xué)習(xí)。

5. 反編譯

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

通過(guò)dis模塊可以將字節(jié)碼反編譯:

>>> 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

字節(jié)碼反編譯后的結(jié)果和匯編語(yǔ)言很類似。其中,第一列是字節(jié)碼的偏移量,第二列是指令,第三列是操作數(shù)。以第一條字節(jié)碼為例,LOAD_CONST指令將常量加載進(jìn)棧,常量下標(biāo)由操作數(shù)給出,而下標(biāo)為0的常量是:

>>> result.co_consts[0]3.14

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

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

>>>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

操作數(shù)指定的常量或名字的實(shí)際值在旁邊的括號(hào)內(nèi)列出,此外,字節(jié)碼以語(yǔ)句為單位進(jìn)行了分組,中間以空行隔開(kāi),語(yǔ)句的行號(hào)在字節(jié)碼前面給出。例如PI = 3.14這個(gè)語(yǔ)句就被會(huì)變成了兩條字節(jié)碼:

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

6. pyc

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

>>> import demo

pyc文件會(huì)保存經(jīng)過(guò)序列化處理的代碼對(duì)象PyCodeObject。這樣一來(lái),Python后續(xù)導(dǎo)入demo模塊時(shí),直接讀取pyc文件并反序列化即可得到代碼對(duì)象,避免了重復(fù)編譯導(dǎo)致的開(kāi)銷。只有demo.py有新修改(時(shí)間戳比.pyc文件新),Python才會(huì)重新編譯。

因此,對(duì)比Java而言:Python中的.py文件可以類比Java中的.java文件,都是源碼文件;而.pyc文件可以類比.class文件,都是編譯結(jié)果。只不過(guò)Java程序需要先用編譯器javac命令來(lái)編譯,再用虛擬機(jī)java命令來(lái)執(zhí)行;而Python解釋器把這兩個(gè)過(guò)程都完成了。

以上就是Python字節(jié)碼與程序執(zhí)行過(guò)程詳解的詳細(xì)內(nèi)容,更多關(guān)于Python程序執(zhí)行字節(jié)碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解如何使用Python網(wǎng)絡(luò)爬蟲(chóng)獲取招聘信息

    詳解如何使用Python網(wǎng)絡(luò)爬蟲(chóng)獲取招聘信息

    在疫情階段,想找一份不錯(cuò)的工作變得更為困難,很多人會(huì)選擇去網(wǎng)上看招聘信息。可是招聘信息有一些是錯(cuò)綜復(fù)雜的。本文將為大家介紹用Python爬蟲(chóng)獲取招聘信息的方法,需要的可以參考一下
    2022-03-03
  • 基于Python實(shí)現(xiàn)射擊小游戲的制作

    基于Python實(shí)現(xiàn)射擊小游戲的制作

    這篇文章主要介紹了如何利用Python制作一個(gè)自己專屬的第一人稱射擊小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起動(dòng)手試一試
    2022-04-04
  • Python+unittest+DDT實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測(cè)試

    Python+unittest+DDT實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測(cè)試

    這篇文章主要介紹了Python+unittest+DDT實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)測(cè)試,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Python面向?qū)ο罂偨Y(jié)及類與正則表達(dá)式詳解

    Python面向?qū)ο罂偨Y(jié)及類與正則表達(dá)式詳解

    Python中的類提供了面向?qū)ο缶幊痰乃谢竟δ埽侯惖睦^承機(jī)制允許多個(gè)基類,派生類可以覆蓋基類中的任何方法,方法中可以調(diào)用基類中的同名方法。這篇文章主要介紹了Python面向?qū)ο罂偨Y(jié)及類與正則表達(dá)式 ,需要的朋友可以參考下
    2019-04-04
  • Python+Sympy實(shí)現(xiàn)計(jì)算微積分

    Python+Sympy實(shí)現(xiàn)計(jì)算微積分

    微積分的計(jì)算也許平時(shí)用不到,會(huì)讓人覺(jué)得有點(diǎn)高深,它們的計(jì)算過(guò)程中需要使用很多計(jì)算規(guī)則,但是使用?Sympy?可以有效減輕這方面的負(fù)擔(dān),本文就來(lái)和大家簡(jiǎn)單講講吧
    2023-07-07
  • 詳解Python如何利用turtle繪制中國(guó)結(jié)

    詳解Python如何利用turtle繪制中國(guó)結(jié)

    春節(jié)是中國(guó)特有的傳統(tǒng)節(jié)日,中國(guó)結(jié)是中華民族特有的純粹的文化精髓,富含豐富的文化底蘊(yùn)。本文將利用turtle繪制一個(gè)中國(guó)結(jié),需要的可以參考一下
    2022-02-02
  • Python實(shí)現(xiàn)去除圖片中指定顏色的像素功能示例

    Python實(shí)現(xiàn)去除圖片中指定顏色的像素功能示例

    這篇文章主要介紹了Python實(shí)現(xiàn)去除圖片中指定顏色的像素功能,結(jié)合具體實(shí)例形式分析了Python基于pil與cv2模塊的圖形載入、運(yùn)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下
    2019-04-04
  • 關(guān)于tf.reverse_sequence()簡(jiǎn)述

    關(guān)于tf.reverse_sequence()簡(jiǎn)述

    今天小編就為大家分享一篇關(guān)于tf.reverse_sequence()簡(jiǎn)述,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-01-01
  • pycharm永久激活超詳細(xì)教程

    pycharm永久激活超詳細(xì)教程

    這篇文章主要介紹了pycharm永久激活超詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • 人生苦短我用python python如何快速入門?

    人生苦短我用python python如何快速入門?

    這篇文章主要教大家如何快速入門python,一個(gè)簡(jiǎn)短而全面的入門教程帶你走入Python的大門,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03

最新評(píng)論