一、Python內存管理(引用計數、垃圾回收)
Python(CPython)采用的是:
“引用計數為主,垃圾回收為輔” 的內存管理機制。
也就是說:
-
引用計數機制:負責大部分內存釋放,簡單、高效。
-
垃圾回收機制(GC):處理循環引用等引用計數解決不了的問題。
1. 引用計數(Reference Counting)
每個 Python 對象都有一個“引用計數”,表示當前有多少個地方在使用它。
一旦引用計數為 0,Python 立刻銷毀對象、釋放內存。
舉例說明:
a = [1, 2, 3] # 創建列表對象,引用計數 = 1
b = a # b 也指向這個列表,對象引用計數 = 2
del a # 刪除 a,引用計數 = 1
del b # 刪除 b,引用計數 = 0,對象被銷毀
可以使用 sys.getrefcount()
查看一個對象的引用計數:
import sys
x = [1, 2, 3]
print(sys.getrefcount(x)) # 注意:結果比你預期的多1,因為參數x也算一次引用
引用計數的優點:
-
實時釋放內存:對象不用的時候,立刻回收。
-
實現簡單:不需要像 Java 那樣復雜的 GC 跑一遍遍。
2. 引用計數的缺陷:循環引用
如果兩個對象互相引用對方,即使外部沒有引用了,引用計數也不會為0,內存就不會釋放,導致 內存泄露。
示例:
class Node:def __init__(self):self.ref = Nonea = Node()
b = Node()
a.ref = b
b.ref = adel a
del b# a和b雖然沒有外部引用,但它們互相引用,所以內存不會釋放!
在這個例子中:
對象 | 誰引用了它? | 引用計數 |
---|---|---|
a | main 作用域、b.ref | 2 |
b | main 作用域、a.ref | 2 |
現在執行 del a
、del b
:
對象 | 誰引用了它? | 引用計數 |
---|---|---|
a | 只剩 b.ref | 1 |
b | 只剩 a.ref | 1 |
引用計數永遠不會變為 0!
3. 垃圾回收(Garbage Collection,GC)
為了解決循環引用的問題,Python 內部設計了一個 GC模塊 來“定期清理”不再使用的內存。
這個 GC 是通過 “分代回收(Generational GC)” 實現的。
分代回收模型(Generational GC)
Python 把所有的對象分成 3 代:
代數 | 含義 | 特點 |
---|---|---|
第0代 | 新建對象 | 最頻繁回收 |
第1代 | 經常存活下來的對象 | 偶爾回收 |
第2代 | 長期存在的對象 | 很少回收,數量最多 |
GC 機制假設:
“活得越久的對象,越有可能繼續活著”,所以越老的代回收越少。
回收的流程
-
Python 會監控“分配了多少個對象”和“刪除了多少個對象”。
-
當分配/刪除數量超過閾值,就觸發 GC:
-
先回收第0代
-
如果仍然觸發閾值,就繼續回收第1代、第2代
-
-
采用標記-清除算法:標記“可達對象”,刪除“不可達對象”
GC 模塊使用示例:
import gc# 查看當前是否啟用了自動GC
print(gc.isenabled()) # True# 手動觸發一次GC
gc.collect()# 查看GC統計信息
print(gc.get_count()) # 返回 (第0代對象數量, 第1代, 第2代)
4. 小對象內存池機制
CPython 還對**小對象(小于 512 字節)**做了優化:
-
使用內存池(稱為 pymalloc)重復利用內存塊。
-
這樣避免頻繁向操作系統申請/釋放內存,提高性能。
這就是為什么寫 a = 10; b = 10
時,兩個變量可能指向的是同一個對象地址。
5. 總結
機制 | 原理 | 優點 | 缺點 |
---|---|---|---|
引用計數 | 每個對象維護引用計數,0即銷毀 | 實時、高效 | 不能處理循環引用 |
垃圾回收 | 分代收集,標記清除 | 彌補引用計數的缺陷 | 增加一些系統開銷 |
小對象池 | 內存池優化小對象 | 提高小對象復用效率 | 占用一些額外內存 |
二、Python解釋器(CPython、PyPy)
Python 解釋器就是把你寫的 .py
源碼翻譯為計算機能執行的“指令”的程序。
目前主流解釋器有多個實現:
名稱 | 語言實現 | 特點 | 適用場景 |
---|---|---|---|
CPython | 用 C 寫的 | 官方標準實現,最常用 | 默認解釋器,穩定 |
PyPy | 用 RPython 寫 | 支持 JIT,速度更快 | 追求性能 |
Jython | 用 Java 寫 | 可運行在 JVM 上,調用 Java | Java 環境 |
IronPython | .NET 實現 | 支持 C#/VB 調用 | .NET 環境 |
MicroPython | C實現(精簡) | 運行在嵌入式設備 | 單片機開發 |
1. CPython(最主流、最重要)
-
CPython 是 Python 的官方實現。
-
是用 C語言 寫的解釋器(Interpreter)。
-
所有你運行
.py
的地方,默認就是 CPython。
$ python3 --version
Python 3.11.7 ← 這就是 CPython
CPython 的執行流程
Python 源碼(.py 文件)↓
詞法/語法分析↓
AST(抽象語法樹)↓
編譯成字節碼(.pyc 文件)↓
交由 CPython 的虛擬機解釋執行
這也是為什么會看到 .pyc
文件,它其實就是:
????????Python 的“中間語言”,類似 Java 的 .class
文件。
CPython 底層結構
typedef struct _object {Py_ssize_t ob_refcnt; // 引用計數PyTypeObject *ob_type; // 類型信息
} PyObject;
CPython(Python 的官方解釋器)就是用 C 語言編寫的
Py_ssize_t ob_refcnt; // 當前對象的引用計數,決定是否自動釋放
PyTypeObject *ob_type; // 指向該對象的類型結構體,比如 int、str 類型
每個 Python 中的對象(int、list、dict 等)在底層都是一個 PyObject 結構體,通過這個結構可以知道:
-
它被引用了多少次(用于自動內存管理)
-
它到底是什么類型(用于運行時類型識別)
CPython 的缺陷:GIL(全局解釋器鎖)
CPython 使用 GIL 來確保多線程安全:
Global Interpreter Lock→ 同一時間,只允許一個線程執行 Python 字節碼
這意味著 Python 的多線程并不能真正并發運行計算密集型任務。CPU 利用率低。
2. PyPy(性能極致的解釋器)
-
PyPy 是用一種叫 RPython(可靜態類型的Python子集) 實現的 Python 解釋器。
-
最大亮點是 JIT(Just-In-Time)編譯技術,可以把熱代碼編譯成機器碼,加速運行。
PyPy 的優勢
特性 | 說明 |
---|---|
?JIT 編譯 | PyPy 會將頻繁執行的代碼“編譯為機器碼”加速運行 |
?智能優化 | 內聯、移除多余變量等操作自動完成 |
? 性能提升 | 實測一般比 CPython 快 4~10 倍 |
例如:
from time import timedef f():s = 0for i in range(10_000_000):s += ireturn sstart = time()
f()
print("耗時:", time() - start)
同樣的代碼,用 PyPy 跑會明顯快很多。
如何下載安裝 PyPy:
1)訪問官網下載頁面:https://www.pypy.org/download.html
2)解壓縮到一個目錄,比如:C:\pypy3.9
3)打開命令行,輸入:
C:\pypy3.9\pypy3.exe
如果是 Linux / Mac 用戶:
sudo apt install pypy3 # Ubuntu
# 或
brew install pypy3 # Mac(需安裝 Homebrew)
4)運行代碼用 PyPy 解釋器
比如:
pypy3 your_script.py
或者:
pypy3
>>> print("hello from PyPy!")
這樣就不再用默認的 CPython 。
三、編譯原理初步(AST抽象語法樹、字節碼)
Python 作為一種 解釋型語言,它的代碼執行過程包括了“編譯”和“解釋”兩個階段:
編譯階段:把 Python 源代碼編譯成一種中間表示形式(字節碼),這個字節碼并不是直接機器碼,而是 Python 虛擬機能理解的低級指令。
解釋執行階段:字節碼由 Python 解釋器(虛擬機)執行。
這兩個過程中的重要組成部分就是 AST(抽象語法樹) 和 字節碼。
1. 什么是 AST(抽象語法樹)?
AST(抽象語法樹) 是對 Python 源代碼的一種樹狀表示,它的每個節點代表了 Python 程序中的一個結構元素(如表達式、語句等)。它比語法樹更簡潔,去除了與編程語言具體語法無關的部分。
-
AST 是 Python 編譯階段的產物。
-
Python 代碼首先通過 詞法分析(將源碼轉換為標記)和 語法分析(將標記轉換為 AST)兩步生成。
-
在 AST 之上,Python 可以進行代碼優化、分析、轉換、生成字節碼等操作。
?AST 的重要性
-
代碼分析與優化:通過 AST,你可以分析代碼的結構,做靜態分析(例如變量的使用、控制流等)或進行代碼優化。
-
代碼轉換:AST 是生成字節碼的基礎,修改 AST 可以進行代碼重寫、反混淆等操作。
-
反向工程:通過分析 AST,你可以將混淆代碼或壓縮代碼還原為易懂的原始代碼結構。
生成 AST
可以使用 Python 的 ast
模塊來分析和操作 Python 的 AST。例如,下面的代碼將 Python 代碼解析成 AST:
import astsource_code = "x = 1 + 2 * 3"
tree = ast.parse(source_code)# 打印 AST 的結構
ast.dump(tree, indent=4)
輸出會是類似這樣的結構(樹狀結構):
Module(body=[Assign(targets=[Name(id='x', ctx=Store())],value=BinOp(left=Num(n=1), op=Add(), right=BinOp(left=Num(n=2), op=Mult(), right=Num(n=3))))]
)
這就是 Python 代碼的 AST 結構。可以看到,它把算式 1 + 2 * 3
轉換成了一個樹狀結構,節點包含了操作符、操作數、表達式等。
AST 操作示例
通過操作 AST,你可以修改代碼結構,比如實現代碼重構、自動化修改等:
class MyTransformer(ast.NodeTransformer):def visit_BinOp(self, node):if isinstance(node.op, ast.Add):node.op = ast.Sub() # 把加法變成減法return node# 修改 AST
transformer = MyTransformer()
transformed_tree = transformer.visit(tree)# 查看修改后的樹
ast.dump(transformed_tree, indent=4)
2. 什么是字節碼(Bytecode)?
字節碼 是一種中間代碼,Python 將源代碼編譯成字節碼后,由 Python 的虛擬機(PVM)解釋執行。它比源代碼更接近機器碼,但仍然獨立于平臺。
-
字節碼的作用:通過將源碼編譯成字節碼,Python 可以實現跨平臺運行,只需要安裝 Python 解釋器,就能執行相同的字節碼。
-
.pyc
文件:字節碼通常保存在.pyc
文件中,位于__pycache__
目錄下。Python 會根據文件的修改時間來決定是否重新編譯。
字節碼的生成過程
-
編譯:Python 源代碼通過 CPython 編譯器轉換為字節碼(
.pyc
文件)。每次你運行.py
文件時,CPython 會首先檢查是否有.pyc
文件,如果沒有就編譯成字節碼。 -
執行:字節碼通過 Python 虛擬機(PVM)解釋執行。
字節碼反匯編
使用 dis
模塊,可以看到 Python 代碼的字節碼。例如:
import disdef example():a = 1 + 2return adis.dis(example)
輸出結果會顯示字節碼:
2 0 LOAD_CONST 1 (1)2 LOAD_CONST 2 (2)4 BINARY_ADD6 STORE_NAME 0 (a)8 LOAD_NAME 0 (a)10 RETURN_VALUE
這說明:
-
先把常量
1
和2
加載到棧上(LOAD_CONST
), -
執行加法(
BINARY_ADD
), -
把結果存到變量
a
中(STORE_NAME
)。
字節碼與機器碼的關系
字節碼比源代碼更接近機器碼,但它仍然不是直接可以由 CPU 執行的代碼。在 CPython 中,字節碼是由 Python 解釋器逐條解釋執行的。而機器碼則是直接由硬件執行的代碼。
3. 結合 AST 和 字節碼的實際應用
應用場景:代碼重構、反混淆
在做 JS 逆向、APP 逆向時,很多時候需要分析混淆代碼并重構它。在 Python 中,AST 是一個非常好的工具,可以幫助理解代碼的結構,進行重構、逆向等操作。
例子:重構混淆代碼
比如,將一段復雜的運算式改寫為更易讀的形式:
# 混淆的代碼
x = 1 + 2 * 3# 解析并重構為
x = 7
通過 AST,可以提取出原始的運算結構,并將它們轉換為更容易理解的形式。
應用場景:性能優化
在性能優化中,了解 Python 如何將源代碼轉化為字節碼,有助于優化代碼,避免不必要的計算、內存分配等操作。例如,避免頻繁的內存分配、減少不必要的對象創建,可以提高程序的性能。
術語 | 含義 | 作用 |
---|---|---|
AST | 抽象語法樹,一種樹狀結構 | 代碼結構分析、重構、靜態分析、代碼生成和優化 |
字節碼 | 中間代碼,比源碼更低級,但不依賴平臺 | Python 跨平臺運行,虛擬機解釋執行 |
四、C擴展開發(如需要極限優化)
C 擴展就是使用 C 語言編寫的 Python 模塊,它可以被 Python 調用,像普通模塊一樣 import
。
為什么要用 C 寫 Python 模塊?
-
Python 本身是用 C 寫的(CPython)
-
Python 解釋型的性能較低,尤其在數值計算、循環密集任務中較慢
-
C 語言性能高,可以編寫性能關鍵模塊,再用 Python 調用
-
還能直接調用操作系統或硬件資源,或者加載第三方底層庫(如 OpenSSL)
實際例子:寫一個簡單的 C 擴展模塊
創建一個 C 模塊 myfast.c
,提供一個加法函數給 Python 調用。
文件結構:
myfast/
├── myfast.c
└── setup.py
1)?myfast.c
內容如下:
#include <Python.h>// C函數:兩個數相加
static PyObject* add(PyObject* self, PyObject* args) {int a, b;// 從 Python 傳入參數解析為 C 中的 intif (!PyArg_ParseTuple(args, "ii", &a, &b)) {return NULL;}// 返回一個 Python 整數return PyLong_FromLong(a + b);
}// 定義方法表
static PyMethodDef MyFastMethods[] = {{"add", add, METH_VARARGS, "Add two numbers"},{NULL, NULL, 0, NULL}
};// 定義模塊
static struct PyModuleDef myfastmodule = {PyModuleDef_HEAD_INIT,"myfast", // 模塊名NULL, // 文檔(可為 NULL)-1,MyFastMethods
};// 初始化模塊
PyMODINIT_FUNC PyInit_myfast(void) {return PyModule_Create(&myfastmodule);
}
2)?setup.py
構建腳本:
from setuptools import setup, Extensionmodule = Extension('myfast', sources=['myfast.c'])setup(name='myfast',version='1.0',description='A demo C extension for Python',ext_modules=[module]
)
3)編譯模塊
運行:
python setup.py build_ext --inplace
這會生成一個 .so
文件(Linux/mac)或 .pyd
(Windows),然后就可以像普通模塊一樣導入:
4)Python 中使用
import myfastprint(myfast.add(3, 5)) # 輸出 8
高級應用方向
調用已有 C/C++ 庫(如 OpenSSL、libcurl)
可以封裝 C 函數為 Python 接口,甚至是使用 extern
引用已有 .so
/.dll
。
寫“黑盒”模塊
很多商業代碼、加密算法,甚至反爬參數邏輯會被寫成 C 模塊,然后 只暴露一個接口給 Python,大大提高逆向難度。
?用于性能關鍵場景
在圖像處理、數據加密、音視頻處理、矩陣運算、機器學習中,Python 常調用 NumPy(內部也是 C 實現)。