中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

python編程的開發機制是什么

發布時間:2020-09-24 12:20:02 來源:億速云 閱讀:186 作者:Leah 欄目:編程語言

這篇文章將為大家詳細講解有關python編程的開發機制是什么,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

如果你曾經寫過或者用過 Python,你可能已經習慣了看到 Python 源代碼文件;它們的名稱以.Py 結尾。你可能還見過另一種類型的文件是 .pyc 結尾的,它們就是 Python “字節碼”文件。(在 Python3 的時候這個 .pyc 后綴的文件不太好找了,它在一個名為pycache的子目錄下面。).pyc文件可以防止Python每次運行時都重新解析源代碼,該文件大大節省了時間。

Python是如何工作的

Python 通常被描述為一種解釋語言,在這種語言中,你的源代碼在程序運行時被翻譯成CPU指令,但這只是說對了部分。和許多解釋型語言一樣,Python 實際上將源代碼編譯為虛擬機的一組指令,Python 解釋器就是該虛擬機的實現。其中這種中間格式稱為“字節碼”。

因此,Python留下的這些.pyc文件,是為了讓運行的速快變得 “更快”,或者是針對你的源代碼的”優化“的版本;它們是 Python 虛擬機上運行的字節碼指令。

Python 虛擬機內幕

CPython使用基于堆棧的虛擬機。也就是說,它完全圍繞堆棧數據結構(你可以將項目“推”到結構的“頂部”,或者將項目“彈出”到“頂部”)。

CPython 使用三種類型的棧:

1.調用堆棧。這是運行中的Python程序的主要結構。對于每個當前活動的函數調用,它都有一個項目一“幀”,堆棧的底部是程序的入口點。每次函數調用都會將新的幀推到調用堆棧上,每次函數調用返回時,它的幀都會彈出

2.在每一幀中,都有一個評估堆棧(也稱為數據堆棧)。這個堆棧是執行 Python 函數的地方,執行Python代碼主要包括將東西推到這個堆棧上,操縱它們,然后將它們彈出。

3.同樣在每一幀中,都有一個塊堆棧。Python使用它來跟蹤某些類型的控制結構:循環、try /except塊,以及 with 塊都會導致條目被推送到塊堆棧上,每當退出這些結構之一時,塊堆棧就會彈出。這有助于Python知道在任何給定時刻哪些塊是活動的,例如,continue或break語句可以影響正確的塊。

大多數 Python 字節碼指令操作的是當前調用棧幀的計算棧,雖然,還有一些指令可以做其它的事情(比如跳轉到指定指令,或者操作塊棧)。

為了更好地理解,假設我們有一些調用函數的代碼,比如這個

my_function(my_variable,2)。

Python 將轉換為一系列字節碼指令:

1.一個LOAD_NAME指令,用于查找函數對象 my_function,并將其推送到計算棧的頂部

2.另一個 LOAD_NAME 指令去查找變量 my_variable,并將其推送到計算棧的頂部

3.一個 LOAD_CONST 指令將一個整數 2 推送到計算棧的頂部

4.一個 CALL_FUNCTION 指令

CALL_FUNCTION 指令有2個參數,它表示 Python 需要在堆棧頂部彈出兩個位置參數; 然后函數將在它上面進行調用,并且它也同時被彈出(關鍵字參數的函數,使用指令-CALL_FUNCTION_KW-類似的操作,并配合使用第三條指令CALL_FUNCTION_EX,它適用于函數調用涉及到參數使用 * 或 ** 操作符的情況)

一旦 Python 具備了這些,它將在調用堆棧上分配一個新的幀,填充到函數調用的本地變量,然后運行該幀內的 my_function 的字節碼。一旦運行完成,幀將從調用堆棧中彈出,在原始幀中,my_function 的返回值將被推入到計算棧的頂部。

我們知道了這個東西了,也知道字節碼了文件了,但是如何去使用字節碼呢?ok不知道也沒關系,接下來的時間我們所有的話題都將圍繞字節碼,在python有一個模塊可以通過反編譯Python代碼來生成字節碼這個模塊就是今天要說的--dis模塊。

dis模塊的使用

dis模塊包括一些用于處理 Python 字節碼的函數,可以將字節碼“反匯編”為更便于人閱讀的形式。查看解釋器運行的字節碼還有助于優化代碼。這個模塊對于查找多線程中的競態條件也很有用,因為可以用它評估代碼中哪一點線程控制可能切換。參考源碼Include/opcode.h,可以找到字節碼的正式列表。詳細可以看官方文檔。注意不同版本的python生成的字節碼內容可能不一樣,這里我用的Python 3.8.

訪問和理解字節碼

輸入如下內容,然后運行它:

def hello()
    print("Hello, World!")
import dis
dis.dis(hello)

函數 dis.dis() 將反匯編一個函數、方法、類、模塊、編譯過的 Python 代碼對象、或者字符串包含的源代碼,以及顯示出一個人類可讀的版本。dis 模塊中另一個方便的功能是 distb()。你可以給它傳遞一個 Python 追溯對象,或者在發生預期外情況時調用它,然后它將在發生預期外情況時反匯編調用棧上最頂端的函數,并顯示它的字節碼,以及插入一個指向到引發意外情況的指令的指針。

它也可以用于查看 Python 為每個函數構建的編譯后的代碼對象,因為運行一個函數將會用到這些代碼對象的屬性。這里有一個查看 hello() 函數的示例:

>>> hello.__code__<code object hello at 0x104e46930, file "<stdin>", line 1>>>> hello.__code__.co_consts
(None, 'Hello, World!')>>> hello.__code__.co_varnames
()>>> hello.__code__.co_names
('print',)

代碼對象在函數中可以以屬性 code 來訪問,并且攜帶了一些重要的屬性:

co_consts是存在于函數體內的任意實數的元組

co_varnames是函數體內使用的包含任意本地變量名字的元組

co_names是在函數體內引用的任意非本地名字的元組

許多字節碼指令--尤其是那些推入到棧中的加載值,或者在變量和屬性中的存儲值--在這些元組中的索引作為它們參數。

因此,現在我們能夠理解 hello() 函數中所列出的字節碼:

1、 LOAD_GLOBAL 0:告訴 Python 通過 co_names (它是 print 函數)的索引 0 上的名字去查找它指向的全局對象,然后將它推入到計算棧

2、 LOAD_CONST 1:帶入 co_consts 在索引 1 上的字面值,并將它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因為 Python 函數調用有一個隱式的返回值 None,如果沒有顯式的返回表達式,就返回這個隱式的值 )。

3、 CALL_FUNCTION 1:告訴 Python 去調用一個函數;它需要從棧中彈出一個位置參數,然后,新的棧頂將被函數調用。

“原始的” 字節碼--是非人類可讀格式的字節--也可以在代碼對象上作為 co_code 屬性可用。如果你有興趣嘗試手工反匯編一個函數時,你可以從它們的十進制字節值中,使用列出 dis.opname 的方式去查看字節碼指令的名字。

基本反匯編

函數dis()可以打印 Python 源代碼(模塊、類、方法、函數或代碼對象)的反匯編表示。可以通過從命令行運行 dis 來反匯編 dis_simple.py 之類的模塊。

dis_simple.py
#!/usr/bin/env python3
# encoding: utf-8
my_dict = {'a': 1}

輸出按列組織,包含原始源代碼行號,代碼對象中的指令地址,操作碼名稱以及傳遞給操作碼的任何參數。

對于簡單的代碼我們可以通過命令行的形式執行下面的命令:

python3 -m dis dis_simple.py

輸出

1           0 LOAD_CONST               0 ('a')
              2 LOAD_CONST               1 (1)
              4 BUILD_MAP                1
              6 STORE_NAME               0 (my_dict)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

在這里源代碼轉換為4個不同的操作來創建和填充字典,然后將結果保存到一個局部變量。

首先解釋每一行各列參數的含義:

以第一條指令為例:

第一列 數字(1)表示對應源代碼的行數。

第二列(可選)指示當前執行的指令(例如,當字節碼來自幀對象時)【這個例子沒有】

第三列 一個標簽,表示從之前的指令到此可能的JUMP 【這個例子沒有】

第四列 數字是字節碼中對應于字節索引的地址(這些是2的倍數,因為Python 3.6每條指令使用2個字節,而在以前的版本中可能會有所不同)指令LOAD_CONST在0位置。

第五列 指令本身對應的人類可讀的名字這里是"LOAD_CONST"

第六列 Python內部用于獲取某些常量或變量,管理堆棧,跳轉到特定指令等的指令的參數(如果有的話)。

第七列 計算后的實際參數。

然后讓我們看看這個過程:

由于 Python 解釋器是基于棧的,所以前幾步是用LOAD_CONST將常量按正確順序放入到棧中,然后使用 BUILD_MAP 彈出要增加到字典的新鍵和值。用 STORE_NAME 將所得到的dict對象綁定名為my_dict.

反匯編函數

需要注意的是上面的命令行反編譯的形式,不能自動的遞歸反編譯函數,所以我們要使用在文件中導入dis的模式進行反編譯,就像下面這樣。

#dis_function.py
def f(*args):
    nargs = len(args)
    print(nargs, args)

if __name__ == '__main__':
    import dis
    dis.dis(f)

運行命令

python3 dis_function.py

然后得到以下結果

2           0 LOAD_GLOBAL              0 (len)
              2 LOAD_FAST                0 (args)
              4 CALL_FUNCTION            1
              6 STORE_FAST               1 (nargs)

  3           8 LOAD_GLOBAL              1 (print)
             10 LOAD_FAST                1 (nargs)
             12 LOAD_FAST                0 (args)
             14 CALL_FUNCTION            2
             16 POP_TOP
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

要查看函數的內部,必須把函數傳遞到dis().因為這里打印的是函數內部的東西,所以沒有顯示函數的在外層的行編號,而是從2開始的。

下面解析下每一行指令的含義:

1、LOAD_GLOBAL 用來加載全局變量,包括指定函數名,類名,模塊名等全局符號,這里是len函數,LOAD_FAST 一般加載局部變量的值,也就是讀取值,用于計算或者函數調用傳參等,這里就是傳入參數args。

2、一般是先指定要調用的函數,然后壓參數,最后通過 CALL_FUNCTION 調用。

3、STORE_FAST 保存值到局部變量。也就是把結果賦值給 STORE_FAST。

4、下面的print因為2個參數所以LOAD_FAST了2次,POP_TOP刪除堆棧頂部(TOS)項。LOAD_CONST加載const變量,比如數值、字符串等等,這里因為是print所以值為None。

5、最后通過RETURN_VALUE來確定函數結尾。

要打印一個函數的總結信息我們可以使用dis的show_code的方法,它包含使用的參數和名的相關信息,show_code的參數就是這個函數對象,代碼如下:

def f(*args):
    nargs = len(args)
    print(nargs, args)

if __name__ == '__main__':
    import dis
    dis.show_code(f)

運行結果

Name:              f
Filename:          dis_function_showcode.py
Argument count:    0
Kw-only arguments: 0
Number of locals:  2
Stack size:        3
Flags:             OPTIMIZED, NEWLOCALS, VARARGS, NOFREE
Constants:
   0: None
Names:
   0: len
   1: print
Variable names:
   0: args
   1: nargs

關于python編程的開發機制是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

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

AI

澳门| 柞水县| 吕梁市| 五莲县| 五河县| 黄梅县| 黄山市| 皮山县| 台江县| 蒙城县| 安顺市| 扎兰屯市| 洮南市| 桦甸市| 贺兰县| 宜昌市| 西藏| 义马市| 福泉市| 交城县| 灌南县| 永福县| 来宾市| 施秉县| 石泉县| 凤冈县| 额敏县| 景洪市| 贡嘎县| 平舆县| 肇州县| 图们市| 清原| 通江县| 闸北区| 安陆市| 嘉荫县| 漠河县| 西充县| 闻喜县| 兴宁市|