『Python』源碼解析_從ctype模塊理解對象

一、對象的引用計數

從c代碼分析可知,python全部對象的內存有着一樣的起始結構:引用計數+類型信息,實際上這些信息在python本體重也是能夠透過包來一窺一二的,python

from ctypes import *

class PyObject(Structure):
    _fields_ = [("refcnt", c_size_t),
                ("typeid", c_void_p)]

a = "this is a string"

# 經過id(a)能夠得到對象a的內存地址,而PyObject.from_address()能夠將
# 指定的內存地址的內容轉換爲一個PyObject對象。經過此PyObject對象obj_a
# 能夠訪問對象a的結構體中的內容。
obj_a = PyObject.from_address(id(a))
print(obj_a.refcnt)
b = [a]*10
print(obj_a.refcnt)

查看對象a的引用次數,原文中返回值1,實際返回2,多是python3相對2的改動使得多引用一次,接下來建立一個列表,此列表中的每一個元素都是對象a,所以此列表應用了它10次,因此引用次數變爲了12。數組

二、對象的類型

查看對象a的類型對象的地址,它和id(type(a))相同,而因爲對象a的類型爲str,所以也就是id(str),app

print(obj_a.typeid)
print(id(type(a)))
print(id(str))

1825992064測試

1825992064ui

1825992064this

驗證一下,從這個地址讀取python對象試試,spa

obj_s = PyObject.from_address(id(str))
print(obj_s.typeid)

1825980464指針

指向新的地址,若是這個地址對應python的類型對象,那麼它的類型應該是元類對象,咱們嘗試一下在源碼解析一書上的知識。對象

三、元類的特殊性質

print(id(type))
type_obj = PyObject.from_address(id(type))
print(type_obj.typeid)

1825980464blog

1825980464

符合,str(對應字符串類)的類型、元類自己的類型的、元類自己,三者的地址都指向同一位置。

四、不可變對象值存儲

這種對象除了有PyObject中的兩個字段以外,還有一個val字段保存實際的值,二者佔用空間也就不一樣,這種不可變對象的內存值部分與頭信息是相連的,因此使用sys.getsizeof能夠一併查到

import sys
print(sys.getsizeof(1))
print(sys.getsizeof(1.0))

28

24

5.一、扁平序列的建立

  • 指定建立元素類型(字符串爲字符,數組爲int或者float)
  • 獲取目標對象長度(即幾個數字,幾個字符)
  • 有了這兩個信息就能夠劃份內存建立對象了
    • 原數據就存儲在內存中,咱們指定結構體去賦予新格式,並加入新的對象頭信息

模擬C語言建立扁平序列的過程,

# 在定義長度不固定的字段時,使用長度爲0的數組定義一個不佔內存的
# 僞字段_val。create_var_object()用來建立大小不固定的結構體對象
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_:
        # 首先搜索名爲_val的字段,並將其類型保存到inner_type中
        if name == "_val":
            inner_type = t._type_
            print(inner_type)
    if inner_type is not None:
        # 而後建立一個PyVarObject結構體讀取obj對象中的size字段
        tmp = PyVarObject.from_address(id(obj))
        size = tmp.size
        # 再經過size字段的大小建立一個對應的Inner結構體類,它可
        # 以從struct繼承,由於struct中的_val字段不佔據內存。
        class Inner(struct):
            _fields_ = [("val", inner_type*size)]
        Inner.__name__ = struct.__name__
        struct = Inner
    return struct.from_address(id(obj))

s = 'asdfgh'
s_obj = create_var_object(PyStr, s)
s_obj.size
<class 'ctypes.c_char'>
6

因爲只是模擬,咱們使用一個新建的僞字符串結構去從真的字符串結構讀取數據格式化解析,並建立新對象。

5.二、(僞)長整型數字

l = 0x1234567890abcd
l_obj = create_var_object(PyLong, l)
print(l_obj.size)
val = list(l_obj.val)
print(val)

<class 'ctypes.c_ushort'>

2

[43981, 14480]

不管是幾進制,實際l的type就是int。這裏面咱們看到了,實際上咱們使用了多個普通的整形數表達一個很大的值,長整型其實是一個序列。

六、List列表類型

列表對象的長度是可變的,所以不能採用字符串那樣的結構體,

而是使用了一個指針字段items指向可變長度的數組,而這個數組自己是一個指向PyObject的指針。allocated字段表示這個指針數組的長度,而size字段表示指針數組中已經使用的元素個數,即列表的長度。列表結構體自己的大小是固定的。

class PyList(PyVarObject):
    # item字段是指針數組,這裏表現爲二級指針
    _fields_ = [("items", POINTER(POINTER(PyObject))),
                ("allocated", c_size_t)]

    def print_field(self):
        # size字段表示指針數組中已經使用的元素個數
        # allocated字段表示這個指針數組的長度
        # 指針字段items指向可變長度的數組
        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 range(10):
        alist_obj.print_field()
        alist.append(x)
test_list()

 一開始列表的長度和其指針數組的長度都是3,即列表處於飽和狀態。所以向列表中添加新元素時,須要從新分配指針數組,所以指針數組的長度變爲了7,而地址也發生了變化。這時列表的長度爲4,所以指針數組中還有3個空位保存新的元素。因爲每次從新分配指針數組時,都會預分配一些額外空間,所以往列表中添加元素的平均時間複雜度爲O(1),

3 3 <cparam 'P' (000001E0A1ABB330)>
4 7 <cparam 'P' (000001E09E0B14D0)>
5 7 <cparam 'P' (000001E09E0B14D0)>
6 7 <cparam 'P' (000001E09E0B14D0)>
7 7 <cparam 'P' (000001E09E0B14D0)>
8 12 <cparam 'P' (000001E0A0832AF0)>
9 12 <cparam 'P' (000001E0A0832AF0)>
10 12 <cparam 'P' (000001E0A0832AF0)>
11 12 <cparam 'P' (000001E0A0832AF0)>
12 12 <cparam 'P' (000001E0A0832AF0)>

刪除元素原理一致,

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()
10000 10000 <cparam 'P' (000001E0A24E63B0)>
10 17 <cparam 'P' (000001E0A24E63B0)>
相關文章
相關標籤/搜索