python 3.3 dict:如何将struct PyDictKeysObject转换为python类?

Les*_*ieK 5 c python dictionary ctypes python-3.x

我正在尝试修改Brandon Rhodes代码例程,它检查CPython字典的内部,以便它适用于CPython 3.3.

我相信我已成功翻译了这个结构.

typedef PyDictKeyEntry *(*dict_lookup_func)
    (PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject ***value_addr);

struct _dictkeysobject {
    Py_ssize_t dk_refcnt;
    Py_ssize_t dk_size;
    dict_lookup_func dk_lookup;
    Py_ssize_t dk_usable;
    PyDictKeyEntry dk_entries[1];
};
Run Code Online (Sandbox Code Playgroud)

我认为以下看起来很好:

from ctypes import Structure, c_ulong, POINTER, cast, py_object, CFUNCTYPE

LOOKUPFUNC = CFUNCTYPE(POINTER(PyDictKeyEntry), POINTER(PyDictObject), 
                       py_object, c_ulong, POINTER(POINTER(py_object)))

class PyDictKeysObject(Structure):
"""A key object"""
_fields_ = [
    ('dk_refcnt', c_ssize_t),
    ('dk_size', c_ssize_t),
    ('dk_lookup', LOOKUPFUNC),
    ('dk_usable', c_ssize_t),
    ('dk_entries', PyDictKeyEntry * 1),
]

PyDictKeysObject._dk_entries = PyDictKeysObject.dk_entries
PyDictKeysObject.dk_entries = property(lambda s: 
    cast(s._dk_entries, POINTER(PyDictKeyEntry * s.dk_size))[0])
Run Code Online (Sandbox Code Playgroud)

这行代码现在可以使用,其中d == {0: 0, 1: 1, 2: 2, 3: 3}:

obj = cast(id(d), POINTER(PyDictObject)).contents  # works!!`
Run Code Online (Sandbox Code Playgroud)

这是我在C struct PyDictObject中的翻译:

class PyDictObject(Structure):  # an incomplete type
    """A dictionary object."""

def __len__(self):
    """Return the number of dictionary entry slots."""
    pass

def slot_of(self, key):
    """Find and return the slot at which `key` is stored."""
    pass

def slot_map(self):
    """Return a mapping of keys to their integer slot numbers."""
    pass

PyDictObject._fields_ = [
    ('ob_refcnt', c_ssize_t),
    ('ob_type', c_void_p),
    ('ma_used', c_ssize_t),
    ('ma_keys', POINTER(PyDictKeysObject)),
    ('ma_values', POINTER(py_object)),  # points to array of ptrs
]
Run Code Online (Sandbox Code Playgroud)

Les*_*ieK 3

我的问题是访问 Cpython 3.3 中实现的 python 字典底层的 C 结构。我从 cpython/Objects/dictobject.c 和 Include/dictobject.h 中提供的 C 结构开始。定义字典涉及三个 C 结构体:PyDictObject、PyDictKeysObject 和 PyDictKeyEntry。每个 C 结构体到 python 的正确翻译如下。评论指出了我需要修复的地方。感谢@eryksun 一路以来对我的指导!

class PyDictKeyEntry(Structure):
"""An entry in a dictionary."""
    _fields_ = [
        ('me_hash', c_ulong),
        ('me_key', py_object),
        ('me_value', py_object),
    ]

class PyDictObject(Structure):
    """A dictionary object."""
    pass

LOOKUPFUNC = CFUNCTYPE(POINTER(PyDictKeyEntry), POINTER(PyDictObject), py_object, c_ulong, POINTER(POINTER(py_object)))

class PyDictKeysObject(Structure):
"""An object of key entries."""
    _fields_ = [
        ('dk_refcnt', c_ssize_t),
        ('dk_size', c_ssize_t),
        ('dk_lookup', LOOKUPFUNC),  # a function prototype per docs 
        ('dk_usable', c_ssize_t),
        ('dk_entries', PyDictKeyEntry * 1),  # an array of size 1; size grows as keys are inserted into dictionary; this variable-sized field was the trickiest part to translate into python
    ]   

PyDictObject._fields_ = [
    ('ob_refcnt', c_ssize_t),  # Py_ssize_t translates to c_ssize_t per ctypes docs
    ('ob_type', c_void_p),     # could not find this in the docs
    ('ma_used', c_ssize_t),
    ('ma_keys', POINTER(PyDictKeysObject)),
    ('ma_values', POINTER(py_object)),  # Py_Object* translates to py_object per ctypes docs
]

PyDictKeysObject._dk_entries = PyDictKeysObject.dk_entries
PyDictKeysObject.dk_entries = property(lambda s: cast(s._dk_entries, POINTER(PyDictKeyEntry * s.dk_size))[0])  # this line is called every time the attribute dk_entries is accessed by a PyDictKeyEntry instance; it returns an array of size dk_size starting at address _dk_entries. (POINTER creates a pointer to the entire array; the pointer is dereferenced (using [0]) to return the entire array); the code then accesses the ith element of the array)
Run Code Online (Sandbox Code Playgroud)

以下函数提供对 python 字典底层的 PyDictObject 的访问:

def dictobject(d):
    """Return the PyDictObject lying behind the Python dict `d`."""
    if not isinstance(d, dict):
        raise TypeError('cannot create a dictobject from %r' % (d,))
    return cast(id(d), POINTER(PyDictObject)).contents
Run Code Online (Sandbox Code Playgroud)

如果 d 是具有键值对的 python 字典,则 obj 是包含键值对的 PyDictObject 实例:

obj = cast(id(d), POINTER(PyDictObject)).contents
Run Code Online (Sandbox Code Playgroud)

PyDictKeysObject 的一个实例是:

key_obj = obj.ma_keys.contents
Run Code Online (Sandbox Code Playgroud)

指向存储在字典的槽 0 中的键的指针是:

key_obj.dk_entries[0].me_key
Run Code Online (Sandbox Code Playgroud)

使用这些类的程序以及探测插入字典中的每个键的哈希冲突的例程位于此处。我的代码是 Brandon Rhodes 为 python 2.x 编写的代码的修改。他的代码在这里