摘要:轉載地址在中一切皆是對象,而在實現的語言中,這些對象只不過是一些比較復雜的結構體而已。由于和引用的是同一個整數對象,因此和的值同時發生了變化。用來創建大小不固定的結構體對象,首先搜索名為的字段,并將其類型保存到中。
轉載地址:http://hyry.dip.jp/tech/slice/slice.html/10
在 Python 中一切皆是對象,而在實現 Python 的 C 語言中,這些對象只不過是一些比較復雜的結構體而已。本文通過 ctypes 訪問對象對應的結構體中的數據,加深對 Python 對象的理解。
對象的兩個基本屬性Python 所有對象結構體中的頭兩個字段都是相同的:
refcnt:對象的引用次數,若引用次數為 0 則表示此對象可以被垃圾回收了。
typeid:指向描述對象類型的對象的指針。
通過 ctypes,我們可以很容易定義一個這樣的結構體:PyObject。
注意:本文只描述在 32 位操作系統下的情況,如果讀者使用的是 64 位操作系統,需要對程序中的一些字段類型做一些改變。
from ctypes import * class PyObject(Structure): _fields_ = [("refcnt", c_size_t), ("typeid", c_void_p)]
下面讓我們用 PyObject 做一些實驗幫助理解這兩個字段的含義:
>>> a = "this is a string" >>> obj_a = PyObject.from_address(id(a)) ? >>> obj_a.refcnt ? 1L >>> b = [a]*10 >>> obj_a.refcnt ? 11L >>> obj_a.typeid ? 505269056 >>> id(type(a)) 505269056 >>> id(str) 505269056
?通過 id(a) 可以獲得對象 a 的內存地址,而 PyObject.from_address()可以將指定的內存地址的內容轉換為一個 PyObject 對象。通過此 PyObject 對象obj_a 可以訪問對象 a 的結構體中的內容。
?查看對象 a 的引用次數,由于只有 a 這個名字引用它,因此值為 1。接下來創建一個列表,此列表中的每個元素都是對象 a,因此此列表應用了它 10 次,?所以引用次數變為了 11。
?查看對象 a 的類型對象的地址,它和 id(type(a)) 相同,而由于對象a的類型為str,因此也就是 id(str)。
下面查看str類型對象的這兩個字段:
>>> obj_str = PyObject.from_address(id(str)) >>> obj_str.refcnt 252L >>> obj_str.typeid 505208152 >>> id(type) 505208152
可以看到 str 的類型就是type。再看看 type 對象:
>>> type_obj = PyObject.from_address(id(type)) >>> type_obj.typeid 505208152
type 對象的類型指針就指向它自己,因為 type(type) is type。
整數和浮點數對象接下來看看整數和浮點數對象,這兩個對象除了有 PyObject 中的兩個字段之外,還有一個 val 字段保存實際的值。因此 Python 中一個整數占用 12 個字節,而一個浮點數占用 16 個字節:
>>> sys.getsizeof(1) 12 >>> sys.getsizeof(1.0) 16
我們無需重新定義 refcnt 和 typeid 這兩個字段,通過繼承 PyObject,可以很方便地定義整數和浮點數對應的結構體,它們會繼承父類中定義的字段:
class PyInt(PyObject): _fields_ = [("val", c_long)] class PyFloat(PyObject): _fields_ = [("val", c_double)]
下面是使用 PyInt 查看整數對象的例子:
>>> i = 2000 >>> i_obj = PyInt.from_address(id(a)) >>> i_obj.refcnt 1L >>> i_obj.val 2000
通過 PyInt 對象,還可以修改整數對象的內容:
修改不可變對象的內容會造成嚴重的程序錯誤,請不要用于實際的程序中。
>>> j = i >>> i_obj.val = 2012 >>> j 2012
由于i和j引用的是同一個整數對象,因此i和j的值同時發生了變化。
結構體大小不固定的對象表示字符串和長整型數的結構體的大小不是固定的,這些結構體在 C 語言中使用了一種特殊的字段定義技巧,使得結構體中最后一個字段的大小可以改變。由于結構體需要知道最后一個字段的長度,因此這種結構中包含了一個 size 字段,保存最后一個字段的長度。在 ctypes 中無法表示這種長度不固定的字段,因此我們使用了動態創建結構體類的方法。
class PyVarObject(PyObject): _fields_ = [("size", c_size_t)] class PyStr(PyVarObject): _fields_ = [("hash", c_long), ("state", c_int), ("_val", c_char*0)] ? class PyLong(PyVarObject): _fields_ = [("_val", c_uint16*0)] def create_var_object(struct, obj): inner_type = None for name, t in struct._fields_: if name == "_val": ? inner_type = t._type_ if inner_type is not None: tmp = PyVarObject.from_address(id(obj)) ? size = tmp.size class Inner(struct): ? _fields_ = [("val", inner_type*size)] Inner.__name__ = struct.__name__ struct = Inner return struct.from_address(id(obj))
?在定義長度不固定的字段時,使用長度為 0 的數組定義一個不占內存的偽字段 _val。 create_var_object() 用來創建大小不固定的結構體對象,?首先搜索名為 _val 的字段,并將其類型保存到 inner_type 中。?然后創建一個PyVarObject 結構體讀取obj對象中的 size 字段。?再通過 size 字段的大小創建一個對應的 Inner 結構體類,它可以從 struct 繼承,因為 struct 中的 _val 字段不占據內存。
下面我們用上面的程序做一些實驗:
>>> s_obj = create_var_object(PyStr, s) >>> s_obj.size 9L >>> s_obj.val "abcdegfgh"
當整數的范圍超過了 0x7fffffff 時,Python 將使用長整型整數:
>>> l = 0x1234567890abcd >>> l_obj = create_var_object(PyLong, l) >>> l_obj.size 4L >>> val = list(l_obj.val) >>> val [11213, 28961, 20825, 145]
可以看到 Python 用了 4 個 16 位的整數表示 0x1234567890abcd,下面我們看看長整型數是如何用數組表示的:
>>> hex((val[3] << 45) + (val[2] << 30) + (val[1] << 15) + val[0]) "0x1234567890abcdL"
即數組中的后面的元素表示高位,每個 16 為整數中有 15 位表示數值。
列表對象列表對象的長度是可變的,因此不能采用字符串那樣的結構體,而是使用了一個指針字段items指向可變長度的數組,而這個數組本身是一個指向 PyObject 的指針。 allocated 字段表示這個指針數組的長度,而 size 字段表示指針數組中已經使用的元素個數,即列表的長度。列表結構體本身的大小是固定的。
class PyList(PyVarObject): _fields_ = [("items", POINTER(POINTER(PyObject))), ("allocated", c_size_t)] def print_field(self): print self.size, self.allocated, byref(self.items[0])
我們用下面的程序查看往列表中添加元素時,列表結構體中的各個字段的變化:
def test_list(): alist = [1,2.3,"abc"] alist_obj = PyList.from_address(id(alist)) for x in xrange(10): alist_obj.print_field() alist.append(x)
運行 test_list() 得到下面的結果:
>>> test_list() 3 3? 4 7 ? 5 7 6 7 7 7 8 12 9 12 10 12 11 12 12 12
?一開始列表的長度和其指針數組的長度都是 3,即列表處于飽和狀態。因此?往列表中添加新元素時,需要重新分配指針數組,因此指針數組的長度變為了 7,而地址也發生了變化。這時列表的長度為 4,因此指針數組中還有 3 個空位保存新的元素。由于每次重新分配指針數組時,都會預分配一些額外空間,因此往列表中添加元素的平均時間復雜度為 O(1)。
下面再看看從列表刪除元素時,各個字段的變化:
def test_list2(): alist = [1] * 10000 alist_obj = PyList.from_address(id(alist)) alist_obj.print_field() del alist[10:] alist_obj.print_field()
運行test_list2()得到下面的結果:
>>> test_list2() 10000 1000010 17
可以看出大指針數組的位置沒有發生變化,但是后面額外的空間被回收了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/37606.html
摘要:上篇文章我許了一個愿,就是想讓大家多多關注我,然后我的粉絲就蹭蹭的漲了好幾百,謝謝大家的厚愛。可是我發現粉絲是漲了,三連變少了,謝謝大家這次給我三連,我一定再接再厲。地址的尋找陽光總值,種植一個豌豆需要,非常不夠用。 目錄 前言 游戲的安裝 思路 ? ? ? 一句話總結 ? ? ? 大概的思...
摘要:調用以回調函數地址為參數的函數這個主題就稍微繞一些了,也就是說在接口中,需要傳入回調函數作為參數。這個問題在中也可以解決,并且回調函數可以用定義。代碼代碼很簡單回調函數的傳入參數為,返回參數也是。 項目中要對一個用 C 編寫的 .so 庫進行邏輯自測。這項工作,考慮到靈活性,我首先考慮用 Python 來完成。 研究了一些資料,采用 python 的 ctypes 來完成這項工作。已經...
摘要:最近了解了提供的一個外部函數庫它提供了語言兼容的幾種數據類型,并且可以允許調用編譯好的庫。這里是閱讀相關資料的一個記錄,內容大部分來自官方文檔。注意,提供的接口會在不同系統上有出入,比如為了加載動態鏈接庫,在上提供的是而在上提供的是和。 參考資料 https://docs.python.org/2.7/l... http://www.ibm.com/developerw... c...
摘要:可以在接口文件中直接引用庫里的內容,大大方便接口文件的編寫。使用庫里的這里先介紹方式通過創建出來的數組是數組的直接代理,非常底層和高效,但是,它也和數組一樣不安全,一樣沒有邊界檢查。對由于這種情況,可以使用庫里的。 如果你也像我們一樣,同時使用Python和C++,以獲得兩種語言的優勢,一定也會希望尋找一種好的方式集成這兩種語言,相比而言,讓Python能夠方便使用C++的庫更加重要,...
摘要:可以使用標準的索引切片迭代操作訪問它,其中每項操作均鎖進程同步,對于字節字符串,還具有屬性,可以把整個數組當做一個字符串進行訪問。當所編寫的程序必須一次性操作大量的數組項時,如果同時使用這種數據類型和用于同步的單獨大的鎖,性能將極大提升。 上一篇文章:Python進程專題5:進程間通信下一篇文章:Python進程專題7:托管對象 我們現在知道,進程之間彼此是孤立的,唯一通信的方式是隊...
閱讀 2477·2023-04-26 02:18
閱讀 1269·2021-10-14 09:43
閱讀 3835·2021-09-26 10:00
閱讀 6981·2021-09-22 15:28
閱讀 2547·2019-08-30 15:54
閱讀 2610·2019-08-30 15:52
閱讀 483·2019-08-29 11:30
閱讀 3473·2019-08-29 11:05