python反編譯學(xué)習(xí)之字節(jié)碼詳解
前言
如果你曾經(jīng)寫(xiě)過(guò)或者用過(guò) Python,你可能已經(jīng)習(xí)慣了看到 Python 源代碼文件;它們的名稱(chēng)以.Py 結(jié)尾。你可能還見(jiàn)過(guò)另一種類(lèi)型的文件是 .pyc 結(jié)尾的,它們就是 Python “字節(jié)碼”文件。(在 Python3 的時(shí)候這個(gè) .pyc 后綴的文件不太好找了,它在一個(gè)名為_(kāi)_pycache__的子目錄下面。).pyc文件可以防止Python每次運(yùn)行時(shí)都重新解析源代碼,該文件大大節(jié)省了時(shí)間。
Python是如何工作的
Python 通常被描述為一種解釋語(yǔ)言,在這種語(yǔ)言中,你的源代碼在程序運(yùn)行時(shí)被翻譯成CPU指令,但這只是說(shuō)對(duì)了部分。和許多解釋型語(yǔ)言一樣,Python 實(shí)際上將源代碼編譯為虛擬機(jī)的一組指令,Python 解釋器就是該虛擬機(jī)的實(shí)現(xiàn)。其中這種中間格式稱(chēng)為“字節(jié)碼”。
因此,Python留下的這些.pyc文件,是為了讓運(yùn)行的速快變得 “更快”,或者是針對(duì)你的源代碼的”優(yōu)化“的版本;它們是 Python 虛擬機(jī)上運(yùn)行的字節(jié)碼指令。
Python 虛擬機(jī)內(nèi)幕
CPython使用基于堆棧的虛擬機(jī)。也就是說(shuō),它完全圍繞堆棧數(shù)據(jù)結(jié)構(gòu)(你可以將項(xiàng)目“推”到結(jié)構(gòu)的“頂部”,或者將項(xiàng)目“彈出”到“頂部”)。
CPython 使用三種類(lèi)型的棧:
1.調(diào)用堆棧。這是運(yùn)行中的Python程序的主要結(jié)構(gòu)。對(duì)于每個(gè)當(dāng)前活動(dòng)的函數(shù)調(diào)用,它都有一個(gè)項(xiàng)目一“幀”,堆棧的底部是程序的入口點(diǎn)。每次函數(shù)調(diào)用都會(huì)將新的幀推到調(diào)用堆棧上,每次函數(shù)調(diào)用返回時(shí),它的幀都會(huì)彈出
2.在每一幀中,都有一個(gè)評(píng)估堆棧(也稱(chēng)為數(shù)據(jù)堆棧)。這個(gè)堆棧是執(zhí)行 Python 函數(shù)的地方,執(zhí)行Python代碼主要包括將東西推到這個(gè)堆棧上,操縱它們,然后將它們彈出。
3.同樣在每一幀中,都有一個(gè)塊堆棧。Python使用它來(lái)跟蹤某些類(lèi)型的控制結(jié)構(gòu):循環(huán)、try /except塊,以及 with 塊都會(huì)導(dǎo)致條目被推送到塊堆棧上,每當(dāng)退出這些結(jié)構(gòu)之一時(shí),塊堆棧就會(huì)彈出。這有助于Python知道在任何給定時(shí)刻哪些塊是活動(dòng)的,例如,continue或break語(yǔ)句可以影響正確的塊。
大多數(shù) Python 字節(jié)碼指令操作的是當(dāng)前調(diào)用棧幀的計(jì)算棧,雖然,還有一些指令可以做其它的事情(比如跳轉(zhuǎn)到指定指令,或者操作塊棧)。
為了更好地理解,假設(shè)我們有一些調(diào)用函數(shù)的代碼,比如這個(gè):
my_function(my_variable,2)。
Python 將轉(zhuǎn)換為一系列字節(jié)碼指令:
1.一個(gè)LOAD_NAME指令,用于查找函數(shù)對(duì)象 my_function,并將其推送到計(jì)算棧的頂部
2.另一個(gè) LOAD_NAME 指令去查找變量 my_variable,并將其推送到計(jì)算棧的頂部
3.一個(gè) LOAD_CONST 指令將一個(gè)整數(shù) 2 推送到計(jì)算棧的頂部
4.一個(gè) CALL_FUNCTION 指令
CALL_FUNCTION 指令有2個(gè)參數(shù),它表示 Python 需要在堆棧頂部彈出兩個(gè)位置參數(shù); 然后函數(shù)將在它上面進(jìn)行調(diào)用,并且它也同時(shí)被彈出(關(guān)鍵字參數(shù)的函數(shù),使用指令-CALL_FUNCTION_KW-類(lèi)似的操作,并配合使用第三條指令CALL_FUNCTION_EX,它適用于函數(shù)調(diào)用涉及到參數(shù)使用 * 或 ** 操作符的情況)
一旦 Python 具備了這些,它將在調(diào)用堆棧上分配一個(gè)新的幀,填充到函數(shù)調(diào)用的本地變量,然后運(yùn)行該幀內(nèi)的 my_function 的字節(jié)碼。一旦運(yùn)行完成,幀將從調(diào)用堆棧中彈出,在原始幀中,my_function 的返回值將被推入到計(jì)算棧的頂部。
我們知道了這個(gè)東西了,也知道字節(jié)碼了文件了,但是如何去使用字節(jié)碼呢?ok不知道也沒(méi)關(guān)系,接下來(lái)的時(shí)間我們所有的話(huà)題都將圍繞字節(jié)碼,在python有一個(gè)模塊可以通過(guò)反編譯Python代碼來(lái)生成字節(jié)碼這個(gè)模塊就是今天要說(shuō)的--dis模塊。
dis模塊的使用
dis模塊包括一些用于處理 Python 字節(jié)碼的函數(shù),可以將字節(jié)碼“反匯編”為更便于人閱讀的形式。查看解釋器運(yùn)行的字節(jié)碼還有助于優(yōu)化代碼。這個(gè)模塊對(duì)于查找多線(xiàn)程中的競(jìng)態(tài)條件也很有用,因?yàn)榭梢杂盟u(píng)估代碼中哪一點(diǎn)線(xiàn)程控制可能切換。參考源碼Include/opcode.h,可以找到字節(jié)碼的正式列表。詳細(xì)可以看官方文檔。注意不同版本的python生成的字節(jié)碼內(nèi)容可能不一樣,這里我用的Python 3.8.
訪(fǎng)問(wèn)和理解字節(jié)碼
輸入如下內(nèi)容,然后運(yùn)行它:
def hello() print("Hello, World!") import dis dis.dis(hello)
函數(shù) dis.dis() 將反匯編一個(gè)函數(shù)、方法、類(lèi)、模塊、編譯過(guò)的 Python 代碼對(duì)象、或者字符串包含的源代碼,以及顯示出一個(gè)人類(lèi)可讀的版本。dis 模塊中另一個(gè)方便的功能是 distb()。你可以給它傳遞一個(gè) Python 追溯對(duì)象,或者在發(fā)生預(yù)期外情況時(shí)調(diào)用它,然后它將在發(fā)生預(yù)期外情況時(shí)反匯編調(diào)用棧上最頂端的函數(shù),并顯示它的字節(jié)碼,以及插入一個(gè)指向到引發(fā)意外情況的指令的指針。
它也可以用于查看 Python 為每個(gè)函數(shù)構(gòu)建的編譯后的代碼對(duì)象,因?yàn)檫\(yùn)行一個(gè)函數(shù)將會(huì)用到這些代碼對(duì)象的屬性。這里有一個(gè)查看 hello() 函數(shù)的示例:
>>> 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',)
代碼對(duì)象在函數(shù)中可以以屬性 __code__ 來(lái)訪(fǎng)問(wèn),并且攜帶了一些重要的屬性:
co_consts 是存在于函數(shù)體內(nèi)的任意實(shí)數(shù)的元組
co_varnames 是函數(shù)體內(nèi)使用的包含任意本地變量名字的元組
co_names 是在函數(shù)體內(nèi)引用的任意非本地名字的元組
許多字節(jié)碼指令--尤其是那些推入到棧中的加載值,或者在變量和屬性中的存儲(chǔ)值--在這些元組中的索引作為它們參數(shù)。
因此,現(xiàn)在我們能夠理解 hello() 函數(shù)中所列出的字節(jié)碼:
1、LOAD_GLOBAL 0:告訴 Python 通過(guò) co_names (它是 print 函數(shù))的索引 0 上的名字去查找它指向的全局對(duì)象,然后將它推入到計(jì)算棧
2、LOAD_CONST 1:帶入 co_consts 在索引 1 上的字面值,并將它推入(索引 0 上的字面值是 None,它表示在 co_consts 中,因?yàn)?Python 函數(shù)調(diào)用有一個(gè)隱式的返回值 None,如果沒(méi)有顯式的返回表達(dá)式,就返回這個(gè)隱式的值 )。
3、CALL_FUNCTION 1:告訴 Python 去調(diào)用一個(gè)函數(shù);它需要從棧中彈出一個(gè)位置參數(shù),然后,新的棧頂將被函數(shù)調(diào)用。
“原始的” 字節(jié)碼--是非人類(lèi)可讀格式的字節(jié)--也可以在代碼對(duì)象上作為 co_code 屬性可用。如果你有興趣嘗試手工反匯編一個(gè)函數(shù)時(shí),你可以從它們的十進(jìn)制字節(jié)值中,使用列出 dis.opname 的方式去查看字節(jié)碼指令的名字。
基本反匯編
函數(shù)dis()可以打印 Python 源代碼(模塊、類(lèi)、方法、函數(shù)或代碼對(duì)象)的反匯編表示??梢酝ㄟ^(guò)從命令行運(yùn)行 dis 來(lái)反匯編 dis_simple.py 之類(lèi)的模塊。
dis_simple.py #!/usr/bin/env python3 # encoding: utf-8 my_dict = {'a': 1}
輸出按列組織,包含原始源代碼行號(hào),代碼對(duì)象中的指令地址,操作碼名稱(chēng)以及傳遞給操作碼的任何參數(shù)。
對(duì)于簡(jiǎn)單的代碼我們可以通過(guò)命令行的形式執(zhí)行下面的命令:
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
在這里源代碼轉(zhuǎn)換為4個(gè)不同的操作來(lái)創(chuàng)建和填充字典,然后將結(jié)果保存到一個(gè)局部變量。
首先解釋每一行各列參數(shù)的含義:
以第一條指令為例:
第一列 數(shù)字(1)表示對(duì)應(yīng)源代碼的行數(shù)。
第二列(可選)指示當(dāng)前執(zhí)行的指令(例如,當(dāng)字節(jié)碼來(lái)自幀對(duì)象時(shí))【這個(gè)例子沒(méi)有】
第三列 一個(gè)標(biāo)簽,表示從之前的指令到此可能的JUMP 【這個(gè)例子沒(méi)有】
第四列 數(shù)字是字節(jié)碼中對(duì)應(yīng)于字節(jié)索引的地址(這些是2的倍數(shù),因?yàn)镻ython 3.6每條指令使用2個(gè)字節(jié),而在以前的版本中可能會(huì)有所不同)指令LOAD_CONST在0位置。
第五列 指令本身對(duì)應(yīng)的人類(lèi)可讀的名字這里是"LOAD_CONST"
第六列 Python內(nèi)部用于獲取某些常量或變量,管理堆棧,跳轉(zhuǎn)到特定指令等的指令的參數(shù)(如果有的話(huà))。
第七列 計(jì)算后的實(shí)際參數(shù)。
然后讓我們看看這個(gè)過(guò)程:
由于 Python 解釋器是基于棧的,所以前幾步是用LOAD_CONST將常量按正確順序放入到棧中,然后使用 BUILD_MAP 彈出要增加到字典的新鍵和值。用 STORE_NAME 將所得到的dict對(duì)象綁定名為my_dict.
反匯編函數(shù)
需要注意的是上面的命令行反編譯的形式,不能自動(dòng)的遞歸反編譯函數(shù),所以我們要使用在文件中導(dǎo)入dis的模式進(jìn)行反編譯,就像下面這樣。
#dis_function.py def f(*args): nargs = len(args) print(nargs, args) if __name__ == '__main__': import dis dis.dis(f)
運(yùn)行命令
python3 dis_function.py
然后得到以下結(jié)果
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
要查看函數(shù)的內(nèi)部,必須把函數(shù)傳遞到dis().因?yàn)檫@里打印的是函數(shù)內(nèi)部的東西,所以沒(méi)有顯示函數(shù)的在外層的行編號(hào),而是從2開(kāi)始的。
下面解析下每一行指令的含義:
1、LOAD_GLOBAL 用來(lái)加載全局變量,包括指定函數(shù)名,類(lèi)名,模塊名等全局符號(hào),這里是len函數(shù),LOAD_FAST 一般加載局部變量的值,也就是讀取值,用于計(jì)算或者函數(shù)調(diào)用傳參等,這里就是傳入?yún)?shù)args。
2、一般是先指定要調(diào)用的函數(shù),然后壓參數(shù),最后通過(guò) CALL_FUNCTION 調(diào)用。
3、STORE_FAST 保存值到局部變量。也就是把結(jié)果賦值給 STORE_FAST。
4、下面的print因?yàn)?個(gè)參數(shù)所以L(fǎng)OAD_FAST了2次,POP_TOP刪除堆棧頂部(TOS)項(xiàng)。LOAD_CONST加載const變量,比如數(shù)值、字符串等等,這里因?yàn)槭莗rint所以值為None。
5、最后通過(guò)RETURN_VALUE來(lái)確定函數(shù)結(jié)尾。
要打印一個(gè)函數(shù)的總結(jié)信息我們可以使用dis的show_code的方法,它包含使用的參數(shù)和名的相關(guān)信息,show_code的參數(shù)就是這個(gè)函數(shù)對(duì)象,代碼如下:
def f(*args): nargs = len(args) print(nargs, args) if __name__ == '__main__': import dis dis.show_code(f)
運(yùn)行之后,結(jié)果如下
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
可以看到返回的內(nèi)容有函數(shù),方法,參數(shù)等信息。
反匯編類(lèi)
上面我們知道了如何反匯編一個(gè)函數(shù)的內(nèi)部,同樣的我們也可以用類(lèi)似的方法反匯編一個(gè)類(lèi)。
我們看一個(gè)例子:
import dis class MyObject: """Example for dis.""" CLASS_ATTRIBUTE = 'some value' def __str__(self): return 'MyObject({})'.format(self.name) def __init__(self, name): self.name = name if __name__ == '__main__': dis.dis(MyObject)
運(yùn)行之和得到如下結(jié)果
Disassembly of __init__:
12 0 LOAD_FAST 1 (name)
2 LOAD_FAST 0 (self)
4 STORE_ATTR 0 (name)
6 LOAD_CONST 0 (None)
8 RETURN_VALUEDisassembly of __str__:
9 0 LOAD_CONST 1 ('MyObject({})')
2 LOAD_METHOD 0 (format)
4 LOAD_FAST 0 (self)
6 LOAD_ATTR 1 (name)
8 CALL_METHOD 1
10 RETURN_VALUE
從整體內(nèi)容來(lái)看,結(jié)果分為了兩部分Disassembly of __init__和Disassembly of __str__,Disassembly就是反匯編的意思。
首先分析__init__部分:
1、然后需要注意的一點(diǎn)是,方法是按照字母的順序列出的,所以在部分,先看到name再看到self,但是他們都是 LOAD_FAST。
2、STORE_ATTR實(shí)現(xiàn)self.name = name。
3、然后LOAD_CONST一個(gè)None和RETURN_VALUE標(biāo)志著函數(shù)結(jié)束。
接下來(lái)分析__str__部分:
1、LOAD_CONST將'MyObject({})'加載到棧
2、然后通過(guò) LOAD_METHOD 調(diào)用字符串format方法。這個(gè)方法是Python3.7新加入的。
3、LOAD_FAST 也就是到了self了。
4、LOAD_ATTR 一般是調(diào)用某個(gè)對(duì)象的方法時(shí)。這里就是self.name的.name操作
5、CALL_METHOD 是 python3.7 新增加的內(nèi)容,這里是執(zhí)行方法。
6、RETURN_VALUE表示函數(shù)的結(jié)束。
上面字符串的拼接我們用了format,之前我一直推薦用f-string,下面就讓我們通過(guò)字節(jié)碼來(lái)分析,為什么f-string比f(wàn)ormat要高快。
代碼其他代碼不變,把return改成以下內(nèi)容:
return f'MyObject({self.name})'
再次執(zhí)行,下面我們只看__str__函數(shù)的部分。
Disassembly of __str__: 9 0 LOAD_CONST 1 ('MyObject(') 2 LOAD_FAST 0 (self) 4 LOAD_ATTR 0 (name) 6 FORMAT_VALUE 0 8 LOAD_CONST 2 (')') 10 BUILD_STRING 3 12 RETURN_VALUE
對(duì)比發(fā)現(xiàn)我們這里沒(méi)有了調(diào)用方法的操作LOAD_METHOD,取而代之使用了用于實(shí)現(xiàn)fstring的FORMAT_VALUE指令。之后通過(guò)BUILD_STRING連接堆棧中的計(jì)數(shù)字符串并將結(jié)果字符串推入堆棧.為什么format慢呢, python中的函數(shù)調(diào)用具有相當(dāng)大的開(kāi)銷(xiāo)。 當(dāng)使用str.format()時(shí),CALL_METHOD 中花費(fèi)的額外時(shí)間是導(dǎo)致str.format()比f(wàn)string慢得多。
使用反匯編調(diào)試
調(diào)試一個(gè)異常時(shí),有時(shí)要查看哪個(gè)字節(jié)碼帶來(lái)了問(wèn)題。這個(gè)時(shí)候就很有用了,要對(duì)一個(gè)錯(cuò)誤周?chē)拇a反匯編,有多種方法。第一種策略是在交互解釋器中使用dis()報(bào)告最后一個(gè)異常。
如果沒(méi)有向dis()傳入任何參數(shù),那么它會(huì)查找一個(gè)異常,并顯示導(dǎo)致這個(gè)異常的棧頂元素的反匯編效果。
命令行上使用
打開(kāi)我的命令行執(zhí)行如下操作:
chennan@chennandeMacBook-Pro-2 ~ python3 Python 3.8.0a3 (v3.8.0a3:9a448855b5, Mar 25 2019, 17:05:20) [Clang 6.0 (clang-600.0.57)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import dis >>> j = 4 >>> i = i + 4 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'i' is not defined >>> dis.dis() 1 --> 0 LOAD_NAME 0 (i) 2 LOAD_CONST 0 (4) 4 BINARY_ADD 6 STORE_NAME 0 (i) 8 LOAD_CONST 1 (None) 10 RETURN_VALUE >>>
行號(hào)后面的-->就是導(dǎo)致錯(cuò)誤的操作碼,一個(gè)LOAD_NAME指令,由于沒(méi)有定義變量i,所以無(wú)法將與這個(gè)名關(guān)聯(lián)的值加載到棧中。
代碼中使用distb
程序還可以打印一個(gè)活動(dòng)的traceback的有關(guān)信息,將它傳遞到distb()方法。
下面的程序中有個(gè)DiviedByZero異常;但是這個(gè)公式有兩個(gè)除法,所以不清楚是哪一部分出錯(cuò),此時(shí)我們就可以使用下面的方法:
dis_traceback.py i = 1 j = 0 k = 3 try: result = k * (i / j) + (i / k) except Exception: import dis import sys exc_type, exc_value, exc_tb = sys.exc_info() dis.distb(exc_tb)
運(yùn)行之后輸出
1 0 LOAD_CONST 0 (1)
2 STORE_NAME 0 (i)2 4 LOAD_CONST 1 (0)
6 STORE_NAME 1 (j)3 8 LOAD_CONST 2 (3)
10 STORE_NAME 2 (k)5 12 SETUP_FINALLY 24 (to 38)
6 14 LOAD_NAME 2 (k)
16 LOAD_NAME 0 (i)
18 LOAD_NAME 1 (j)
--> 20 BINARY_TRUE_DIVIDE
22 BINARY_MULTIPLY
24 LOAD_NAME 0 (i)
26 LOAD_NAME 2 (k)
28 BINARY_TRUE_DIVIDE
...
>> 96 END_FINALLY
>> 98 LOAD_CONST 3 (None)
100 RETURN_VALUE
結(jié)果反映的字節(jié)碼很長(zhǎng)我們不用全看了,看最開(kāi)始出現(xiàn)--> 就可以知道錯(cuò)誤的位置了。
其中SETUP_FINALLY 字節(jié)碼的含義是將try塊從try-except子句推入塊堆棧。
這里可以看出將LOAD_NAME 將j壓入棧之后就報(bào)錯(cuò)了。所以可以推斷出在(i/j)就出錯(cuò)了。
參考資料
- https://docs.python.org/zh-cn/3.7/library/dis.html#opcode-STORE_FAST
- https://opensource.com/article/18/4/introduction-python-bytecode
- https://hackernoon.com/a-closer-look-at-how-python-f-strings-work-f197736b3bdb
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Python入門(mén)教程(二十四)Python的迭代器
這篇文章主要介紹了Python入門(mén)教程(二十四)Python的迭代器,Python是一門(mén)非常強(qiáng)大好用的語(yǔ)言,也有著易上手的特性,本文為入門(mén)教程,需要的朋友可以參考下2023-04-04Flask項(xiàng)目搭建配置項(xiàng)導(dǎo)入教程
這篇文章主要為大家介紹了Flask項(xiàng)目搭建配置項(xiàng)導(dǎo)入教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11淺談Python中threading join和setDaemon用法及區(qū)別說(shuō)明
這篇文章主要介紹了淺談Python中threading join和setDaemon用法及區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05對(duì)python numpy.array插入一行或一列的方法詳解
今天小編就為大家分享一篇對(duì)python numpy.array插入一行或一列的方法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01python 調(diào)用pyautogui 實(shí)時(shí)獲取鼠標(biāo)的位置、移動(dòng)鼠標(biāo)的方法
今天小編就為大家分享一篇python 調(diào)用pyautogui 實(shí)時(shí)獲取鼠標(biāo)的位置、移動(dòng)鼠標(biāo)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08基于python分享一款地理數(shù)據(jù)可視化神器keplergl
這篇文章主要介紹了分享一款地理數(shù)據(jù)可視化神器keplergl,keplergl是由Uber開(kāi)源的一款地理數(shù)據(jù)可視化工具,通過(guò)keplergl我們可以在Jupyter?notebook中使用,下文分享需要的小伙伴可以參考一下2022-02-02徹底弄懂Python中的回調(diào)函數(shù)(callback)
回調(diào)函數(shù)就是一個(gè)通過(guò)函數(shù)指針調(diào)用的函數(shù),下面這篇文章主要給大家介紹了關(guān)于Python中回調(diào)函數(shù)(callback)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06利用Python封裝MySQLHelper類(lèi)實(shí)現(xiàn)數(shù)據(jù)庫(kù)的增刪改查功能
Python 連接 MySQL 的方法有很多,常用的有 pymysql 和 mysql-connector-python 兩種庫(kù),本文主要介紹了如何封裝一個(gè)MySQLHelper類(lèi),實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的增刪改查功能,感興趣的可以了解一下2023-06-06python?tkinter實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python?tkinter實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02在matlab中創(chuàng)建類(lèi)似字典的數(shù)據(jù)結(jié)構(gòu)方式
這篇文章主要介紹了在matlab中創(chuàng)建類(lèi)似字典的數(shù)據(jù)結(jié)構(gòu)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03