AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

當創建一個 Python 對象時,背后都經歷了哪些過程?

來源: 責編: 時間:2024-05-21 17:30:25 113觀看
導讀楔子本篇文章來聊一聊對象的創建,一個對象是如何從無到有產生的呢?>>> n = 123>>> n123比如在終端中執行 n = 123,一個整數對象就被創建好了,但它的背后都發生了什么呢?帶著這些疑問,開始今天的內容。Python 為什么這么慢前

楔子

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

本篇文章來聊一聊對象的創建,一個對象是如何從無到有產生的呢?c0128資訊網——每日最新資訊28at.com

>>> n = 123>>> n123

比如在終端中執行 n = 123,一個整數對象就被創建好了,但它的背后都發生了什么呢?帶著這些疑問,開始今天的內容。c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

Python 為什么這么慢

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

前面我們介紹了 Python 對象在底層的數據結構,知道了 Python 底層是通過 PyObject 實現了對象的多態。所以我們先來分析一下 Python 為什么慢?c0128資訊網——每日最新資訊28at.com

在 Python 中創建一個對象,會分配內存并進行初始化,然后用一個 PyObject * 指針來維護這個對象,當然所有對象都是如此。因為指針是可以相互轉化的,所以變量在保存一個對象的指針時,會將指針轉成 PyObject * 之后再交給變量保存。c0128資訊網——每日最新資訊28at.com

因此在 Python 中,變量的傳遞(包括函數的參數傳遞)實際上傳遞的都是泛型指針 PyObject *。這個指針具體指向什么類型的對象我們并不知道,只能通過其內部的 ob_type 字段進行動態判斷,而正是因為這個 ob_type,Python 實現了多態機制。c0128資訊網——每日最新資訊28at.com

比如 a.pop(),我們不知道 a 指向的對象到底是什么類型,它可能是列表、也可能是字典,或者是我們實現了 pop 方法的自定義類的實例對象。至于它到底是什么類型,只能通過 ob_type 動態判斷。c0128資訊網——每日最新資訊28at.com

如果 a 的 ob_type 為 &PyList_Type,那么 a 指向的對象就是列表,于是會調用 list 類型中定義的 pop 操作。如果 a 的 ob_type 為 &PyDict_Type,那么 a 指向的對象就是字典,于是會調用 dict 類型中定義的 pop 操作。所以變量 a 在不同的情況下,會表現出不同的行為,這正是 Python 多態的核心所在。c0128資訊網——每日最新資訊28at.com

再比如列表,它內部的元素也都是 PyObject *,因為類型要保持一致,所以對象的指針不能直接存(因為類型不同),而是需要統一轉成泛型指針 PyObject * 之后才可以存儲。當我們通過索引獲取到該指針進行操作的時候,也會先通過 ob_type 判斷它的類型,看它是否支持指定的操作。所以操作容器內的某個元素,和操作一個變量并無本質上的區別,它們都是 PyObject *。c0128資訊網——每日最新資訊28at.com

從這里我們也能看出來 Python 為什么慢了,因為有相當一部分時間浪費在類型和屬性的查找上面。c0128資訊網——每日最新資訊28at.com

以變量 a + b 為例,這個 a 和 b 指向的對象可以是整數、浮點數、字符串、列表、元組、甚至是我們自己實現了 __add__ 方法的類的實例對象。因為 Python 的變量都是 PyObject *,所以它可以指向任意的對象,因此 Python 就無法做基于類型的優化。c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

首先 Python 底層要通過 ob_type 判斷變量指向的對象到底是什么類型,這在 C 的層面至少需要一次屬性查找。然后 Python 將每一個算術操作都抽象成了一個魔法方法,所以實例相加時要在類型對象中找到該方法對應的函數指針,這又是一次屬性查找。找到了之后將 a、b 作為參數傳遞進去,這會產生一次函數調用,會將對象維護的值拿出來進行運算,然后根據相加的結果創建一個新的對象,再將對象的指針轉成 PyObject * 之后返回。c0128資訊網——每日最新資訊28at.com

所以一個簡單的加法運算,Python 內部居然做了這么多的工作,要是再放到循環里面,那么上面的步驟要重復 N 次。而對于 C 來講,由于已經規定好了類型,所以 a + b 在編譯之后就是一條簡單的機器指令,因此兩者在效率上差別很大。c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

當然我們不是來吐槽 Python 效率的問題,因為任何語言都有擅長的一面和不擅長的一面,這里只是通過回顧前面的知識來解釋為什么 Python 效率低。因此當別人問你 Python 為什么效率低的時候,希望你能從這個角度來回答它,主要就兩點:c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

  • Python 無法基于類型做優化;
  • Python 對象基本都存儲在堆上;

建議不要一上來就談 GIL,那是在多線程情況下才需要考慮的問題。而且我相信大部分覺得 Python 慢的人,都不是因為 Python 無法利用多核才覺得慢的。c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

Python 的 C API

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

然后來說一說 Python 的 C API,這個非常關鍵。首先 Python 解釋器聽起來很高大上,但按照陳儒老師的說法,它不過就是用 C 語言寫出的一個開源軟件,從形式上和其它軟件并沒有本質上的不同。c0128資訊網——每日最新資訊28at.com

比如你在 Windows 系統中打開 Python 的安裝目錄,會發現里面有一個二進制文件 python.exe 和一個動態庫文件 python312.dll。二進制文件負責執行,動態庫文件則包含了相應的依賴,當然編譯的時候也可以把動態庫里的內容統一打包到二進制文件中,不過大部分軟件在開發時都會選擇前者。c0128資訊網——每日最新資訊28at.com

既然解釋器是用 C 寫的,那么在執行時肯定會將 Python 代碼翻譯成 C 代碼,這是毫無疑問的。比如創建一個列表,底層就會創建一個 PyListObject 實例,比如調用某個內置函數,底層會調用對應的 C 函數。c0128資訊網——每日最新資訊28at.com

所以如果你想搞懂 Python 代碼的執行邏輯或者編寫 Python 擴展,那么就必須要清楚解釋器提供的 API 函數。而按照通用性來劃分的話,這些 API 可以分為兩種。c0128資訊網——每日最新資訊28at.com

  • 泛型 API;
  • 特定類型 API;

泛型 API

顧名思義,泛型 API 和參數類型無關,屬于抽象對象層。這類 API 的第一個參數是 PyObject *,可以處理任意類型的對象,API 內部會根據對象的類型進行區別處理。c0128資訊網——每日最新資訊28at.com

而且泛型 API 的名稱也是有規律的,具有 PyObject_### 這種形式,我們舉例說明。c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

對象是如何創建的

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

創建對象可以使用泛型 API,也可以使用特定類型 API,比如創建一個浮點數。c0128資訊網——每日最新資訊28at.com

使用泛型 API 創建

PyObject* pi = PyObject_New(PyObject, &PyFloat_Type);

通過泛型 API 可以創建任意類型的對象,因為該類 API 和類型無關。那么問題來了,解釋器怎么知道要給對象分配多大的內存呢?c0128資訊網——每日最新資訊28at.com

在介紹類型對象的時候我們提到,對象的內存大小、支持哪些操作等等,都屬于元信息,而元信息會存在對應的類型對象中。其中 tp_basicsize 和 tp_itemsize 負責指定實例對象所需的內存空間。c0128資訊網——每日最新資訊28at.com

// Include/objimpl.h#define PyObject_New(type, typeobj)  ((type *)_PyObject_New(typeobj))        // Objects/object.cPyObject *_PyObject_New(PyTypeObject *tp){    // 通過 PyObject_Malloc 為對象申請內存,申請多大呢?    // 會通過 _PyObject_SIZE(tp) 進行計算    PyObject *op = (PyObject *) PyObject_Malloc(_PyObject_SIZE(tp));    if (op == NULL) {        return PyErr_NoMemory();    }    // 設置對象的類型和引用計數    _PyObject_Init(op, tp);    return op;}// Include/cpython/objimpl.hstatic inline size_t _PyObject_SIZE(PyTypeObject *type) {    // 返回類型對象的 tp_basicsize    return _Py_STATIC_CAST(size_t, type->tp_basicsize);}

泛型 API 屬于通用邏輯,而內置類型的實例對象一般會采用特定類型 API 創建。c0128資訊網——每日最新資訊28at.com

使用特定類型 API 創建

// 創建浮點數,值為 2.71PyObject* e = PyFloat_FromDouble(2.71);// 創建一個可以容納 5 個元素的元組PyObject* tpl = PyTuple_New(5);// 創建一個可以容納 5 個元素的列表// 當然這是初始容量,列表是可以擴容的PyObject* lst = PyList_New(5);

和泛型 API 不同,使用特定類型 API 只能創建指定類型的對象,因為該類 API 是和類型綁定的。比如我們可以用 PyDict_New 創建一個字典,但不可能創建一個集合出來。c0128資訊網——每日最新資訊28at.com

如果使用特定類型 API,那么可以直接分配內存。因為內置類型的實例對象,它們的定義在底層都是寫死的,解釋器對它們了如指掌,因此可以直接分配內存并初始化。c0128資訊網——每日最新資訊28at.com

比如通過 e = 2.71 創建一個浮點數,解釋器看到 2.71 就知道要創建 PyFloatObject 結構體實例,那么申請多大內存呢?顯然是 sizeof(PyFloatObject),直接計算一下結構體實例的大小即可。c0128資訊網——每日最新資訊28at.com

圖片圖片c0128資訊網——每日最新資訊28at.com

顯然一個 PyFloatObject 實例的大小是 24 字節,所以內存直接就分配了。分配之后將 ob_refcnt 初始化為 1、ob_type 設置為 &PyFloat_Type、ob_fval 設置為 2.71 即可。c0128資訊網——每日最新資訊28at.com

同理可變對象也是一樣,因為字段都是固定的,內部容納的元素有多少個也可以根據賦的值得到,所以內部的所有字段占用了多少內存可以算出來,因此也是可以直接分配內存的。c0128資訊網——每日最新資訊28at.com

還是那句話,解釋器對內置的數據結構了如指掌,因為這些結構在底層都是定義好的,源碼直接寫死了。所以解釋器根本不需要借助類型對象去創建實例對象,它只需要在實例對象創建完畢之后,將 ob_type 設置為指定的類型即可(讓實例對象和類型對象建立聯系)。c0128資訊網——每日最新資訊28at.com

所以采用特定類型 API 創建實例的速度會更快,但這只適用于內置的數據結構,而我們自定義類的實例對象顯然沒有這個待遇。假設通過 class Person: 定義了一個類,那么在實例化的時候,顯然不可能通過 PyPerson_New 去創建,因為底層壓根就沒有這個 API。c0128資訊網——每日最新資訊28at.com

這種情況下創建 Person 的實例對象就需要 Person 這個類型對象了,因此自定義類的實例對象如何分配內存、如何進行初始化,需要借助對應的類型對象。c0128資訊網——每日最新資訊28at.com

總的來說,Python 內部創建一個對象有兩種方式:c0128資訊網——每日最新資訊28at.com

  • 通過特定類型 API,用于內置數據結構,即內置類型的實例對象。
  • 通過調用類型對象去創建(底層會調用泛型 API),多用于自定義類型。

c0128資訊網——每日最新資訊28at.com

[] 和 list(),應該使用哪種方式

c0128資訊網——每日最新資訊28at.com

c0128資訊網——每日最新資訊28at.com

lst = [] 和 lst = list() 都負責創建一個空列表,但這兩種方式有什么區別呢?c0128資訊網——每日最新資訊28at.com

我們說創建實例對象可以通過解釋器提供的特定類型 API,用于內置類型;也可以通過實例化類型對象去創建,既可用于自定義類型,也可用于內置類型。c0128資訊網——每日最新資訊28at.com

# 通過特定類型 API 創建>>> lst = [] >>> lst[]# 通過調用類型對象創建>>> lst = list()  >>> lst[]

還是那句話,解釋器對內置數據結構了如指掌,并且做足了優化。c0128資訊網——每日最新資訊28at.com

  • 看到 123,就知道創建 PyLongObject 實例;
  • 看到 2.71,就知道創建 PyFloatObject 實例;
  • 看到 ( ),就知道創建 PyTupleObject 實例;
  • 看到 [ ],就知道創建 PyListObject 實例;
  • ······

這些都會使用特定類型 API 去創建,直接為結構體申請內存,然后設置引用計數和類型,所以使用 [ ] 創建列表是最快的。c0128資訊網——每日最新資訊28at.com

但如果使用 list() 創建列表,那么就產生了一個調用,要進行參數解析、類型檢測、創建棧幀、銷毀棧幀等等,所以開銷會大一些。c0128資訊網——每日最新資訊28at.com

import timestart = time.perf_counter()for _ in range(10000000):    lst = []end = time.perf_counter()print(end - start) """0.2144167000001289"""start = time.perf_counter()for _ in range(10000000):    lst = list()end = time.perf_counter()print(end - start) """0.4079916000000594"""

通過 [ ] 的方式創建一千萬次空列表需要 0.21 秒,但通過 list() 的方式創建一千萬次空列表需要 0.40 秒,主要就在于 list() 是一個調用,而 [ ] 直接會被解析成 PyListObject,因此 [ ] 的速度會更快一些。c0128資訊網——每日最新資訊28at.com

所以對于內置類型的實例對象而言,使用特定類型 API 創建要更快一些。而且事實上通過類型對象去創建的話,會先調用 tp_new,然后在 tp_new 內部還是調用了特定類型 API。c0128資訊網——每日最新資訊28at.com

比如:c0128資訊網——每日最新資訊28at.com

  • 創建列表:可以是 list()、也可以是 [ ];
  • 創建元組:可以是 tuple()、也可以是 ( );
  • 創建字典:可以是 dict()、也可以是 { };

前者是通過類型對象去創建的,后者是通過特定類型 API 創建。但對于內置類型而言,我們推薦使用特定類型 API 創建,會直接解析為對應的 C 一級數據結構,因為這些結構在底層都是已經實現好了的,可以直接用。而無需通過諸如 list() 這種調用類型對象的方式來創建,因為它們內部最終還是使用了 特定類型 API,相當于多繞了一圈。c0128資訊網——每日最新資訊28at.com

不過以上都是內置類型,而自定義的類型就沒有這個待遇了,它的實例對象只能通過它自己創建。比如 Person 這個類,解釋器不可能事先定義一個 PyPersonObject 然后將 API 提供給我們,所以我們只能通過 Person() 這種調用類型對象的方式來創建它的實例對象。c0128資訊網——每日最新資訊28at.com

另外內置類型被稱為靜態類,它和它的實例對象在底層已經被定義好了,無法動態修改。我們自定義的類型被稱為動態類,它是在解釋器運行的過程中動態構建的,所以我們可以對其進行動態修改。c0128資訊網——每日最新資訊28at.com

這里需要再強調一點,Python 的動態性、GIL 等特性,都是解釋器在將字節碼翻譯成 C 代碼時動態賦予的,而內置類型在編譯之后已經是指向 C 一級的數據結構,因此也就喪失了相應的動態性。不過與之對應的就是效率上的提升,因為運行效率和動態性本身就是魚與熊掌的關系。c0128資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-89706-0.html當創建一個 Python 對象時,背后都經歷了哪些過程?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 高級程序員必須要會的五種編程范式

下一篇: Python 代碼重構的十個關鍵策略

標簽:
  • 熱門焦點
  • 紅魔電競平板評測:大屏幕硬實力

    前言:三年的疫情因為要上網課的原因激活了平板市場,如今網課的時代已經過去,大家的生活都恢復到了正軌,這也就意味著,真正考驗平板電腦生存的環境來了。也就是面對著這種殘酷的
  • 太卷!Redmi MAX 100英寸電視便宜了:12999元買Redmi史上最大屏

    8月5日消息,從小米商城了解到,Redmi MAX 100英寸巨屏電視日前迎來官方優惠,到手價12999元,比發布價便宜了7000元,在大屏電視市場開卷。據了解,Redmi MAX 100
  • 如何通過Python線程池實現異步編程?

    線程池的概念和基本原理線程池是一種并發處理機制,它可以在程序啟動時創建一組線程,并將它們置于等待任務的狀態。當任務到達時,線程池中的某個線程會被喚醒并執行任務,執行完任
  • 共享單車的故事講到哪了?

    來源丨海克財經與共享充電寶相差不多,共享單車已很久沒有被國內熱點新聞關照到了。除了一再漲價和用戶直呼用不起了。近日多家媒體再發報道稱,成都、天津、鄭州等地多個共享單
  • 騰訊蓋樓,字節拆墻

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之“想重溫暴刷深淵、30+技能搭配暴搓到爽的游戲體驗嗎?一起上晶核,即刻暴打!”曾憑借直播騰訊旗下代理格斗游戲《DNF》一
  • 東方甄選單飛:有些鳥注定是關不住的

    作者:彭寬鴻來源:華爾街科技眼‍‍‍‍‍‍‍‍‍‍東方甄選創始人俞敏洪帶隊的“7天甘肅行”直播活動已在近日順利收官。成立后一
  • 微博大門常打開,迎接海外畫師漂洋東渡

    作者:互聯網那些事“起猛了,我能看得懂日語了”。“為什么日本人說話我能聽懂?”“中文不像中文,日語不像日語,但是我竟然看懂了”…&hell
  • AI芯片初創公司Tenstorrent獲三星和現代1億美元投資

    Tenstorrent是一家由芯片行業資深人士Jim Keller領導的加拿大初創公司,專注于開發人工智能芯片,該公司周三表示,已經從現代汽車集團和三星投資基金等
  • 三星Galaxy Z Fold5官方渲染圖曝光:13.4mm折疊厚度依舊感人

    據官方此前宣布,三星將于7月26日在韓國首爾舉辦Unpacked活動,屆時將帶來帶來包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy Z Flip 5、
Top