本文同步刊載於 「為你自己學 Python - 來讀 CPython 原始碼」
如果這是你第一次聽到 CPython 這個名字,也許你會以為這又是一款新的 Python 實作品。其實 CPython 不是什麼新玩具,它就是大家常在用的 Python 本尊。因為 Python 是使用 C 語言開發的,所以通常講到它的時候,沒特別聲明的話指的就是 CPython。
為什麼喔?沒有為什麼,就好玩而已,好玩很重要!
看到這裡,如果各位預期透過閱讀 CPython 讓你的撰寫 Python 的功力大增,那很可能要失望了。閱讀 CPython 原始碼的確會增進 Python 的功力,但不多。CPython 的原始碼大部份都是 C 語言寫的,所以硬要說的話,閱讀 CPython 原始碼比較能增進你的 C 語言的功力。
所以,如果你只是想要學習 Python 語法的話,只要看「為你自己學 Python」的入門應用篇就很足夠了。閱讀 CPython 的原始碼,最主要是可以了解 Python 背後的黑魔法,例如 Python 的物件是什麼東西、模組是怎麼載入的、Python 的記憶體管理之類的
雖然整個 CPython 專案都可以直接在 GitHub 網站上看的到,但追原始碼這件事,建議還是把整個 CPython 的原始碼拉一份到自己電腦裡慢慢研究比較有效率。如果不知道怎麼使用 Git 下載原始碼的話,可以參考我另一本書「為你自己學 Git」的教學。
另外,我這整個系列單元使用的 CPython 版本都是 3.12.6
,不同的版本之間的原始碼可能會差滿多的,特別是 Minor 版本以上的變動會差更多。所以如果你想要跟著我一起追原始碼的話,建議跟我使用相同版本,比較能確保我們可以看到一樣的內容。
因為 CPython 的原始碼可能有時候會有點複雜,所以我可能會視情況省略部份程式碼,例如原本可能像這樣:
// 檔案:Include/object.h
struct _object {
_PyObject_HEAD_EXTRA
#if (defined(__GNUC__) || defined(__clang__)) \
&& !(defined __STDC_VERSION__ && __STDC_VERSION__ >= 201112L)
// On C99 and older, anonymous union is a GCC and clang extension
__extension__
#endif
#ifdef _MSC_VER
// Ignore MSC warning C4201: "nonstandard extension used:
// nameless struct/union"
__pragma(warning(push))
__pragma(warning(disable: 4201))
#endif
union {
Py_ssize_t ob_refcnt;
#if SIZEOF_VOID_P > 4
PY_UINT32_T ob_refcnt_split[2];
#endif
};
#ifdef _MSC_VER
__pragma(warning(pop))
#endif
PyTypeObject *ob_type;
};
中間有好幾個 #ifdef
的條件編譯指令,這些大部份時候可能跟我們要講的重點比較沒關係,所以我會視情況把它拿掉,原本的程式碼看起來會變成這樣:
struct _object {
_PyObject_HEAD_EXTRA
union {
Py_ssize_t ob_refcnt;
};
PyTypeObject *ob_type;
};
別誤會,並不是那些拿掉的東西不重要,而是把這些判斷拿掉比較能讓重點更聚焦。
不少開發工具都能用來追原始碼,例如 Vim、Visual Studio Code(以下簡稱 VSCode)這樣的文字編輯器,或是 Visual Studio 之類功能完整的 IDE,這些工具都有很好的原始碼閱讀功能,像是很快的跳到函數或常數的定義或是搜尋關鍵字等等,都能幫助我們更有效率的閱讀原始碼。
雖然各位從文字可能看不出來,但在這個系列文章中,我會使用 VSCode 來追原始碼,如果你還沒有安裝的話,可以到 VSCode 官網 下載安裝。
除了 VSCode 之外,最近我有個新歡叫做 Zed,這是個用 Rust 開發的文字編輯器,效能比 VSCode 好很多,特別遇到檔案比較多或檔案比較大的時候,效能表現是肉眼可見的那種等級的差別,也推薦給各位。
先說結論:就算沒有 100% 看懂也沒關係,不求甚解也無妨,過程還是能學到不少東西。
學習並不是一直線的,不需要完全看懂才能有所收穫,有時候只是看看原始碼的結構、函數的呼叫、變數的命名就能有所收穫。就像拼圖一樣,不一定要把每塊拼得完美,只要把整體的輪廓拼出來,就能看到一幅完整的畫。
別擔心,我也不會 C 語言,幸運的是這個世代有 AI/GPT 可以抱大腿,遇到看不懂的,就丟給 GPT 幫忙解釋,所以我也是邊看原始碼的過程一邊學 C 語言,就算是瞎子摸象,摸久後也能摸出一些東西來。
而且整個 CPython 專案現在也不全然都是用 C 語言寫的,裡面有些模組是用 Python 寫的,如果你原本就會一些 Python 的話,這些應該會比較友善一些。
不過如果你是完全的新手,沒寫過任何一種程式語言,那可能會有點難跟上,因此我會預期你對程式語言有一些基本的認識,不一定要是 Python,會其它程式語言也可以,知道什麼是變數、函數、迴圈、流程控制之類的基本觀念應該差不多就夠了。
因為在閱讀原始碼的過程中我可能會試著用 printf()
函數印一些資料出來看看,所以可能也得學一下 C 語言的 Hello World 怎麼寫。所以,接下來我們就動手來寫個 C 語言版的 Hello World 吧!
哇!怎麼才第一章就要動手寫程式啦!別怕,在 C 語言寫 Hello World 沒想像中的難,就跟平常各位在 Python 要寫 Hello World 差沒多少。在 Python 你大概會建立一個 .py
檔一樣,在 C 語言就新增一個 .c
的檔案就好,這裡我就讓它叫做 hello.c
,你想換成別的名字也沒可以。檔案內容如下:
// 檔案:hello.c
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
printf()
函數來印點東西出來,這需要引入 C 語言的函式庫 <stdio.h>
,這裡包含了執行 printf()
函數所需要的宣告。main()
是整個程式的主要進入點,每個 C 程式都規定要有一個 main 函數,待會執行這個程式的時候會從這裡開始執行。printf()
就是這整段程式碼的主角啦,由它把 Hello World
給印出來。return 0
表示程式結束,在慣例上如果回傳一個非 0 的值通常表示執行有問題,這裡我就回傳一個 0
表示正常執行完畢。不像 Python 或 JavaScript 可以直接執行 .py
或 .js
檔,C 語言需要先把 .c
檔進行編譯成可執行檔才能執行。
C 語言的編譯器有好幾款,而且不同的作業系統各有不同的編譯器,像是 clang
或是 gcc
,在 Windows 上的話可找看看 Visual Studio(注意,不是 VSCode 喔), 或是安裝 WSL(Windows Subsystem for Linux)來使用 Linux 上的編譯器,更多詳細資訊可參考 Python Developer's Guide 網站的介紹。同時,這個網站上也有很多關於 CPython 開發的資訊,有興趣的話可以看看。
我的電腦是 macOS,這裡我用 clang
對我剛才寫的 hello.c
進行編譯:
$ clang hello.c -o helloworld
後面的 -o
是指要把原本的 hello.c
編譯並輸出成 helloworld
執行檔,如果編譯過程沒出錯的話,應該會在當前的目錄看到這個檔案,然後就可以執行了:
$ ./helloworld
Hello World
恭喜!你的 C 語言初體驗就完成了!
下個章節,我們就一起來看一下 CPython 的專案結構,順便在你的電腦編譯 CPython 原始碼!
本文同步刊載於 「為你自己學 Python - 來讀 CPython 原始碼」