Python源碼讀后小結


Python 筆記

前言(還是叫雜記吧)

  1. 在python中一切皆對象, python中的對象體系大致包含了"類型對象", "Mapping對象(dict)", "Sequence對象(list, set, tuple, string)", "Number對象(integer, float, boolean)" 以及 "Python虛擬機自己使用的對象"
  2. 在Python中所有的對象都是一個結構體, 所有對象的父類的結構體是
	#define PyObject_HEAD \
		int ob_refcount;		\
		struct ob_type *ob_ref;
	
	typdef struct {
		PyObject_HEAD   // 在每一個其他的結構體中都定義一個PyObject_HEAD, 為將來的多態打下基礎
	} PyObject;
	
  1. 在Python中的一個整型對象是
	typedef struct {
		PyObject_HEAD
		long int_val;
	} PyInt_Object;
	
在Python中一個Sequence對象是

注意: 由於序列的大小是變化的, 所以有定義一個PyObject_VARHEAD封裝了PyObject_HEAD以及序列中元素的大小, 好確定空間

	#define PyObject_VARHEAD \
	PyObject\_HEAD   \
	long size;
	
	typedef struct {
		PyObject_VARHEAD
	} PyStrObject;
	
  1. 創建一個對象時, 先創建一個類型對象(類型對象自始至終都是只要一個的, 在C源碼中, 就是定義了一個全局的變量), 保存要創建對象的類型信息, 接着再在該類型對象的方法中創建指定的對象, 並將類型對象傳遞進入最為該對象的屬性, 如創建一個int對象, 先PyInt_Type對象創建封裝了信息之后再創建PyIntObject對象
  2. Python相比較於其他語言的好處是其doc文檔就在程序之中, 通過PyTypeObject結構體中的doc屬性,
    可以看到Python之父真的不嫌累, 自己打了那么多的幫助手冊的宏

Python中的整型對象

在python中為了提高程序運行的效率, 有小整數池和通用整數池

  1. 小整數池

    	小整數池的范圍通過宏來定義的, 默認是-5-257, 我們可以通過修改此處的宏來調整小整數池的大小, 但是需要對python進行重新編譯
    	小整數池是一個靜態的數組, 在此數組的基礎上又建立了鏈表
    	static PyIntObject *small_ints[262]
    	由此上面的代碼可知, 在這個數組中存放是PyIntObject類型的指針, 我們已經知道了在一個PyObject結構體中都有一個PyTypeObject類型的指針, 
    	Python利用這個類型對象充當單向鏈表的鏈, 即
    	value_one_pyobject->ob_type = next_pyobject;
    	next_pyobject->ob_type = dbnext_pyobject;
    	
    	注意: 在python程序啟動時此整數對象池還沒有初始化, 但是一旦初始化了其中一個對象, 則那個對象就會一個存在, 知道程序結束
    	
    	NOTE!! 在數組上建立的鏈表是通過一個PyIntBlock結構體和一個PyIntObject *類型的free_list創建的, 我們在創建通用整數池時再講
    
  2. 通用整數池

    	通用整數池的實現核心是:
    		typedef _intblock {
    			struct _intblock *next;
    			PyIntObject objects[max_contain];
    		} PyBlockObject;
    			
    		PyBlockObject *block_list = NULL; // 代表着一個塊
    		PyIntObject *free_list = NULL; // 總是指向在block中維護的數組的下
    		一個需要被分配空間的位置, 有該free_list調用fill_free_list
    		函數創建出一個PyBlockObject
    

    block的內存圖
    以上是單個block的情況, 其中在objects存儲的直接就是一個PyIntObject結構體了
    在該結構體中通過ob_type類型的指針形成一個單向鏈表



    以上是多個block的情況, block_list指針總是指向最新生成的block結構體
    如果第一個block中的objects的又有了空間的空間了, 為了避免空間的浪費, 在刪除那個
    對象時, 調用了int_dealloc方法, 有意思的是該方法並不會將空間釋放歸還給操作系統, 而是繼續過該objects數組所有, 在該方法中有一個這樣的操作: 因為objects數組也是一個鏈表, 隨意我們可是使用指針進行索引, 在刪除一個對象時, 將當期的free_list指針指向該對象所在的空間, 接着讓我們刪除的對象的ob_type指針指向剛才free_list指向的位置, 總而言之, int_dealloc是一個偽釋放函數

  3. 小整數池的創建

    前面已經提到過了, 我們在static修飾的數組中存放整數, 在創建一個PyIntObject對象時, 也是通過block和freelist機制實現的, 首先通過free_list構建出一個可變的鏈表, 接着該鏈表中存放的就是
    我們需要的PyIntObject結構體, 在數組中存放其引用並指向他即可

    通過上圖可知, 在static數組中的指針所執行的結構體分布在在幾個個block維護的objects數組中
    當一個block中的objects滿時再創建next

  4. 創建整型對象的方法
    PyInt_FromLong()
    PyInt_FromString(): "123" --> 123
    PyInt_FromUnicode()

  5. 每一個PyObject都有一個hash值, 存儲在ob_type指針所指向的結構體中

    該結構體中包含了類型信息, 函數族, 變量

字符串

  1. 字符串的結構體

    	typedef struct {
    		PyObject_VARHEAD
    		int ob_sstate; // 記錄該字符串對象是否納入了interned(實質上就是一個dict)機制
    		long ob_shash; // 保存字符串對象的hash值, 默認為-1, 用來緩存一個PyStringObject的hash值
    		char ob_sval[1]; // 用於存儲一個字符, 如果是一個字符串則在該位置多申請空間
    	} PyStringObject;
    
  2. 字符串的緩沖

以上是PyObject_FromString()函數創建字符串對象的過程


- 先判斷字符長度, 如果為1, 則在字符串中的256個字節的緩沖區中找 - 如果大於1, 創建新的字符串對象, 默認是進行interned的, 申請sizeof(PyStringObject) + size個空間, 將需要保存的字符串通過memcpy函數拷貝到ob_sval指針的控制域中
  1. PyStringObject的interned機制的實現

  2. 使用"+"可以對字符串進行連接, 但是需要向system頻繁地申請空間, 效率低

    官方建議使用"string".join(str)的形式, 只想內存申請一塊大的空間

    因為:PyStringObject是不定長且不可變的

列表對象

  1. python中的列表對象與C++中的vector對象實現是一樣的

  2. 列表的結構體

    	typedef struct {
    		PyObject_VARHEAD
    		PyObject *item; // 存儲PyObject*的數組指針
    		int size;  // 元素數量
    		int allocted; // 最大容量
    	} PyListObject;
    
  3. 對列表進行插入, 刪除的操作對應的C語言函數為SetItem, GetItem

  4. 為了加快程序運行的速率, Python對List對象使用了緩沖, 有一個free_lists數組, 用來存放已經被刪除的List對象, 其中的item, size, allocted為NULL, 0, 0, 有num_free_lists整數型變量用來記錄在free_list中空閑的List個數, 每一次創建List對象時都會先判斷num_free_lists的值是否為0, 如果為0, 則向內存申請空間創建一個PyListObject, 否則則直接獲取在free_lists中的空間的PyListObject對象!

  5. 細說SetItem

    在List中刪除一個元素, 如果使用了如下的代碼

    	>>> lst = [1, 2, 3, 4]	
    	>>> lst[1:3] = []
    	>>> lst
    	>>> [1, 4]
    	
    	>>> lst[0:] = [2, 3]
    	>>> lst
    	>>> [2, 3]
    

    其中在Python中SetItem有進行判斷, 如果傳入的PyObject為NULL則為刪除元素

    如果傳入的不為空則替換之

  6. 為了節省內存空間, 在插入一個元素的時候, 如果size < allocted / 2, 則壓縮空間

    如果allocted / 2 <= size <= allocted 則直接插入, 必要時進行realloc(類似vector)

  7. 在PyListObject中的ob_type指針所執行的結構體中的ob_free函數並不會將內存歸還OS, 而只是進行了清理操作

dict

  1. 結構體

    	typedef struct {
    		PyObject_HEAD
    		int fill;
    		int used;
    		int mask;
    		PyDictEntry *table; // 在PyDictObject中存放的元素較多時使用
    		PyDictEntry *(*lookup)(PyDictObject *self, PyObject *key, long hash) // 沖突鏈上的搜索函數
    		PyDictEntry *small_table[8]; // 一個存放PyDictEntry的數組, 在PyDictObject中存放的元素較少時使用
    	} PyDictObject;
    	
    	typedef struct {
    		long hash; // 緩存對象的hash值
    		PyObject *key;
    		PyObject *value;
    	} PyDictEntry; // 在Python中不是對象
    

    內存圖


2. 注意: 在PyDictObject中存放是Entry, entry中存放的是鍵值對
3. Java中的dict使用的是"數組 + 鏈表", 而在Python中使用的是"開放地址法", 如果發生了沖突則在調用PyDictObject的lookup方法尋找下一個符合條件的位置, 返回一個可用的Entry, 對其進行賦值
4. Dict也采用了類似List的對象緩沖池, 此池在一開始時什么也沒有, 只有在一個Dict對象銷毀時才會有一個元素, 使用num_free_dict, free_dicts數組

  1. 作用

PyDictObject在python中用處極為廣泛, 我們在python腳本中定義一個a = 1,
對應在C源碼中就是根據整數1創建一個PyIntObject對象, 並將"a"字符串最為key, PyIntObject指針作為value存入到一個EnvironmentDict中, 這樣python在訪問變量a時就有了依據, 只需要從EnvironmentDict中以"a"為鍵找value, 其中"a"已經被hash過了

Python虛擬機

  1. PyCodeObject
    PyCodeObject是通過Scanner, Parser編譯生成的, 存在於內存中的對象, 在Python腳本中一個名字空間對應一個PyCodeObject對象, 一個PyCodeObject對象可能會嵌套一個小的PyCodeObject對象, python編譯生成PyCodeObject對象並將其寫入到硬盤中named .pyc, 用於之后的加快速度, 該文件主要包括magic number, python source code created date, PyCodeObject byte


    magic number: 用於解決python版本之間的兼容問題

    date: 用於判斷是否需要重新編譯源文件

    pycodeobject: 最重要的一部分, 存放的源代碼的信息, 存放着讓python虛擬機運行的執行集合


    注意: 在PyCodeObject中不會保存List對象, 而是保存其中的值

  2. PyFrameObject
    PyFramObject是一個執行環境, 每調用一個函數就會創建一個PyFrameObject對象, 其中包含了PyCodeObject, 總而言之, 一個PyFrameObject對應一個PyCodeObject, PyFrameObject類似於OS的函數壓棧, 有一個f_back用於保存之前函數調用的位置, 在當前函數執行完畢之后就回到原來的函數的棧位置


    python代碼的執行就是在這個PyFrameObject中執行的, pythonVM通過PyFrameObject對象中保存的PyCodeObject來執行Python字節碼, 並且命名空間也與PyFrameObject有關, 其有locals, globals, builtins字典(查找次序是local, global, builtin, 對於module來說, local與global是一樣的), 用於保存對應的域的名字, local就是當前的frame中的變量, global就是當前模塊中在函數之外定義的變量

    python虛擬機並不是直接對PyCodeObject進行直接的操作, 而是以PyFrameObject對象為參數獲取他的屬性PyCodeObject, 通過CodeObject中的code, first, next, last來操作code字符串數組的執行, 通過一個for(;;)循環中套着一個巨大的switch case語句, 可以說這就是一個python虛擬機了, 就是這么一個函數而已

  3. RuntimeEnvironment
    其中的系統棧是python虛擬機提供的

    以上就是python虛擬機根據字節碼執行在棧和local名字空間之間的轉換, 最后的棧會變為空棧, 而存儲在了local的字典中, 首先根據python虛擬機自己認可的字節碼創建PyObject對象(int, string等), 壓入棧, 接着在放到local中
    字節碼的讀取順序一般是自左向右

  4. Python虛擬機的字節碼

    BUILD_MAP: 背后對應的case內容是創建一個map
    BUILD_LIST: 創建一個list
    POP: 出棧
    TOP: 返回棧頂的元素, 但是不出棧
    LOAD_NAME: 根據指定的的變量名字符串, 在local中搜索指定到對應的Object並壓棧
    STORE_NAME: 將棧中的object彈出並存放到local中
    
    對於迭代的語句
    都會先進行SET_LOOP: 正式進入到循環中
    接着在迭代list時會從PyFrameObject中得到PyTryBlock, 該PyTryBlock也是PyObject對象, 他里面存放的是python虛擬機的狀態信息, 在ListObject中的ob_type指向的類型對象中存放這一個listiterobject對象, 獲取該對象, 該iterobject對象中封裝了一個調用他的ListObject, 並且有一個int類型的變量存放元素在ListObject中的位置, 這樣這個迭代器就可以迭代ListObject中的元素了, 迭代完了之后, 將棧恢復到迭代之前(python根據PyTryBlock恢復的)
    
  5. Python中的異常處理機制

    1. 比如在遇到ZeroDivError異常時, 虛擬機會判斷被除數是否為0, 如果為0則break出虛擬機中的switch-case語句(這樣python虛擬機不就停止了嗎? 其實python虛擬機的這個函數是一個遞歸調用的函數, 調用一次python中的函數就會創建一個PyFrameObject對象, 而FrameObject又會對應一個遞歸地Python虛擬機的PyEval_EvalFrame_Exc函數, 該這個Frame中的Eval函數只執行該Frame對應的那那些指令, 這樣就不會斷掉了, 成為棧展開), 並將當前的異常對象一起報錯的異常信息存儲到當前對應的線程狀態對象中去

      在虛擬機初始化環境的時候, 一開始_PyThreadState_Current = NULL, 后來通過PyTread_New返回一個PyTheadState對象賦值給_PyThreadState_Current, 用來當做當前的線程, 而PyThreadState_GET就是一個(_PyThreadState_Current)內容的宏

    2. python虛擬機異常的處理

    3. python中的函數(PyFunctionObject)的調用機制, 其中用的命名空間是他維護的PyCodeObject中的globals, 在執行函數時會創建一個PyFrameObject, 它會取出PyFunctionObject中維護的PyCodeObject中的globals

    4. python解釋器在遇到def語句時就會創建一個PyFuncObject對象, 保存相應的參數信息(有參數或者沒有參數), 接着在調用該函數時, 會將funcobject加載到運行時棧(所謂運行時棧, 顧名思義就是python程序在執行時的一個計算空間, 所有的加減乘除, 方法的調用都要在運行時棧中完成, 而數據的存儲則在別處)中, 在將參數自左向右入棧, 因每一個函數會對應一個比他大一個的PyFrameObject, 所以在call_function函數中就會創建一個維護當前FunctionObject的FrameObject, 接着將棧中的參數都移動到棧的開頭的再上面的位置, 比如叫對
      a += 2, 則將a對象壓入到棧中, 計算完結果在將結果更新到棧之前的a對象的位置, 完成計算

    5. 其他類型的參數就不提了

    6. 注意: python在指定def func(a, name=[]): 時, 編譯器會將func, a, name, []都存起來, 這里的[]存了起來, 就產生了陷阱了

Python虛擬機中的類機制

  1. 在Python2.2之前, 內置的對象, 也就是有Guido自己定義的在Python虛擬機初始化完成之后就被創建的類型對象是不可以被繼承的, 如int, list等, 比如
class A(int):
	def __add__(self, value):
		return 10

當使用a + a1時會報錯, 因為python虛擬機不能知道該方法, 雖然繼承過來了

在Python2.2之后, 為PyTypeObject添加了tp_dict屬性, 該屬性表示一個dict對象, 在編譯時
並沒有確定他保存的元素, 而在Python虛擬機在執行字節碼指令時就會初始化這些對象, 首先創建dict,
里面的entry是類似於"__add__":PyIntObject中的NumbersMethods結構體中的int_add函數執行的地址, 這樣一步一步的初始化, 當然在初始化時采用了和Java一樣的機制, 動態加載, 在初始化一個PyTypeObject類型的對象的實例時, 會先判斷該實例的父類是否初始化完成, 如果沒有則遞歸調用初始化函數來初始化父類的dict, 這里的初始化對象指的都是PyTypeObject, 所以一般其父類都是為NULL, 只有個別的如PyBoolTypeObject的實例的base是PyIntTypeObject, 這樣以后再調用方法時就可以有兩種方式調用了, 不過我們在C源碼級別還是只有一種方式, 因為他是動態生成的dict!, 這樣在a + a1時, 會自動調用__add__函數, 而虛擬機遇到__add__函數就會調用A類重寫了的__add__函數, 總之可以調用而不會出錯

以上為類之間的關系, 其中最右側時示例對象, 他們真的是純粹的實例對象, 而中間的class對象, 即是類型, 因為通過他們可以new出實例對象來, 還是type類型的實例對象, 因為他們都是通過最右側的type類型創建的, 所有的類型的基類都是object, type也是object的子類

我們來梳理一下:
	instance對象: 
		C源碼級別:
			PyStrObject結構體創建出來的對象, PyIntObject..., PyListObject...
		Python源碼級別:
			"Hello world", 1, [1, 2, "Hello world"]
	class對象: 
		C源碼級別: 
			PyIntObject, PyStrObject, PyListObject
			
			他們每一個都是單例的, 通過PyType_Type結構體創建出來的
		對應在Python源碼級別:
			int, str, list
	type類型:
		C源碼級別:
			PyType_Type
		Python:
			type
			
			
			
上面提到的動態的填充tp_dict, 我們再聯系一下Python中的an_instance.__dict__, 發現這兩個東西的名字特別的相似, 其實我們通過an_instance.__dict__顯示的東西就是這里tp_dict中維護的值

當我們使用"asd".__class__, 在python源代碼中就是獲取strobj->ob_type對象

在class中每一個Codeblock中定義的都是該class的屬性, 也就是A.__dict__中顯示的內容, 包括函數和類中的全局變量, 而在函數中定義的屬性

比如
	def __init__(self):
		self.value = "value"
		
則是給了該class創建的instance的對象中的__dict__
		

繼承的實現

  1. 創建class對象時,

    	遇到class語句, PVM會調用該class所在module對應的PyCodeObject中的co_code維護的字
    	節碼, 加載class, 雖然class的聲明和實現在邏輯上是一體的, 但是在PyCodeObject中是分開來存放的, 這與function_object是一樣的, 在moduel中的codeobject中維護的指令集合中只有
    

class的聲明的指令, 加載一些參數, 而在functionObject中也是如此, 之后轉向class名字空間對應
的codeobject, 指令里面的指令真正的創建一個class對象, 在C層次上就是類似於PyType PyStrObject = {};

	在執行聲明class的指令時, PVM會根據繼承的對象, 實現MRO機制
	
	Python對於int, str等內置對象采用的靜態的方式創建的, 就是PyType_Type PyStrType = {...};
	
	而用戶定義的class對象就不是動態生成的了, 也就是說我們不能和str類型對象的創建一樣, 直接使用一個大括號括起來, 在里面賦值
	而是PyType_Type user_define_type;
	user_define_type.tp_name = "Person"; // 你懂的, 就是class Person(object):中的"Person"
	user_define_type.tp_type = &PyType_Type;
	user_define_type.bases = some;
	user_define_type.tp_as_numbers = numbers;
	user_define_type.tp_as_sequence = sequences;
	user_define_type.tp_as_mappging = mappgings;
	還有一些函數就從父類中獲取了, 因為用戶定義的結構體有一個bases, 通過他我們就可以訪問到父類的函數了, 這樣一直延伸到object類型對象
	要調用子類沒有而父類有的方法, 需要去到父類中的dict中維護的"函數名":函數, 中找, 找到就調用
```

![](https://images2018.cnblogs.com/blog/1418650/201806/1418650-20180608172248921-2102201543.png)
    ![](https://images2018.cnblogs.com/blog/1418650/201806/1418650-20180608172258519-974726381.png)
    ![](https://images2018.cnblogs.com/blog/1418650/201806/1418650-20180608172310962-1951291964.png)

Python虛擬機初始化

1. Python虛擬機初始化的時候會創建許多的內置模塊, 首先創建的就是__builtin__ module, 其實我們當前的文件就是一個module, 在Python內置對象中有一個進程對象, 里面維護了一個modules的map,
它是由來存儲所有模塊的名字和模塊對象鍵值對的, 因為每一個module又會有一些函數, 屬性等等, 所以在每一個模塊對象中又會有一個map, 用來存儲module中的鍵值對, 對於__builtin__ module來說, Python
虛擬機首先創建出一個空的ModuleObject, 接着讓里面填充域和其他信息, 我們知道在使用Python時有一些內置的函數, 比如len, dir等等, 這就是填充的內容, 以鍵值對的形式填充: "add":AddFunctionObject, 等等, 調用add方法時其實就是內置的每一個python對象都遵守了add函數調用的協議:)
	注意: 這里內置的add, len函數, 在Python源碼中時PyMethodDef, 反正只要是內置的東西, 他就和我們使用python編寫的函數或者對象本質上就是不一樣的, 比如一個"abc"的字符串對象, 我們查看"abc".__add__, 顯示的是<method-wrapper '__add__' of str object at 0x106cc7308>, 而我們定義一個class A對象, 里面添加一個__add__函數, 而A.__add__顯示的是<function __main__.A.__add__(self)>, 雖然我們定義的class A對象遵守了"+"的協議, 定義a = A(), b = A(), a + b 時會自動調用__add__, 但是這時的__add__已經和內置的不一樣了
	
注意: sys.modules是全局名字空間, 這里的全局是指一個Python進程, 並且全局名字空間不會受import ... as ... 語句中重命名的影響, 其實sys.modules叫做modules pool
	
2. Python除了會加載__builtin__ module, 還有加載一個非常重要的module, 就是我們常用了sys module, 這也模塊中sys.path可以查看python的查找路徑, sys.modules顯示所有已經加載到內存中的
module
	注意: 一開始我們會以為python只是加載了__builtin__到內存中, 其實並不是這樣的, python在初始化的時候就已經加載了大量的內置module到內存中, 只是沒有顯示出來而已, 也就是沒有在命名空間中
顯示, 我們知道, 我們要訪問一個對象, 就是從命名空間去找, 所以現在使用不了那么多已經加載到內存中的內置模塊, 而使用了import關鍵字加載內置模塊時, python會直接從內存中找到那個module對象, 將他
的引用返回, 並且放在用戶可以訪問到的名字空間中, 不管是import內置模塊還是用戶自定義的模塊, 都會更新sys.modules這一個全局模塊的名字空間, 如果重復導入一個模塊, 只需要從sys.modules中放回那個
模塊就行了, 這就是python防止重復導入的原理

3. import加載一個package(一個文件夾)時, import pacname.some, 我們只是想要加載some文件, 但是python會一同將pacname也加載進來, 是想一個下, 我們是怎么訪問some, 是通過pacname.some,
由此可見我們使用該some時是需要pacname, 所以加載包對python來說是必須的, 這是的some不在local名字空間中, 而是在pacname的屬性中

4. 如果我們需要只加載我們需要的, 則使用from ... import , 但是實質上還是同第3點是一樣的, 還是會加載pacname, 但是some也會被放在locals名字空間中, 並將其映射為pacname.some, 通過from我們
可以做到精准加載

5. 使用del語句刪除一個module, 其實只是刪除了其符號在當前名字空間的位置位置, 在全局的module pool中還是存在的, 所有就算刪除了, 我們還是可以在module pool中找到該對象
6. 使用reload(module)函數可以對一個模塊進行動態加載(或者說是更新), 如果在一個模塊中添加了 a = 10, 則調用了reload時, 就是在該module維護的dict中添加一個鍵值對, 而不是
重新創建一個module對象,  

Python中的PyCFuncObject(內置的函數len, dir, 都是這個數據結構), PyMethodObject, PyMethodDef(內置對象的方法都是這個數據結構)

	在Python中內置對象中的方法與用戶自定義的方法是不同的數據結構, 用戶自定義的類中的方法都是PyMethodObject數據結構, 而內置的對象(int, str, list, dict)則是使用了PyCFuncObject和PyMethodDef來實現的, 首先PyMethodDef封裝了PyCFuncObject, 而PyCFuncObject中有一個指針指向了那個PyMethodDef
	
	注意: 以上實質對非魔法內置函數的討論
	對於一個list實例lt, lt.append(1), 就會遍歷list中methoddef數組, 找到名為'append'的methoddef, 取出對應的PyMethodDef, 再獲取其中的PyFuncObject, 得到已經內置好的append函數指針, 調用該函數即可
	而對應一個用戶自定義的class, 調用方法a, 也會查找字典, 找到'f', 取出對應的PyMethod, 里面維護這codeobject, 根據其中的字節碼執行邏輯, 如果沒有重寫內置父類方法的話則還是上述的過程
	
	對於魔法方法或函數:
		Python采用的是slotdef與descriptor的組合, slotdef中封裝了__add__字符串對應的函數指針, 而descriptor封裝了slotdef, 訪問__add__方法時, 通過__add__字符串找到descriptor, 接着訪問其中的函數指針, 調用即可。 注意: 該模式是為了用戶可以繼承內置對象, 在2.0之前是不能繼承內置對象的, 因為如何訪問函數指針會出現問題, 所以在2.2只有加入了tp_dict專門來解決內置的魔法方法的調用, 而其他的內置對象的非魔法方法已經在原始版本的python中通過MethodDef和MemberDef數組解決了, 里面存儲了名字和PyCFuncObject(維護這函數指針)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM