2014-04-27 18 views
5

Sto cercando di modificare il codice Brandon Rhodes Routines that examine the internals of a CPython dictionary in modo che funzioni per CPython 3.3.python 3.3 dict: come convertire struct PyDictKeysObject in python?

Credo di aver tradotto questa struttura con successo.

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]; 
}; 

Credo che il seguente guarda bene ora:

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]) 

Questa riga di codice ora funziona, dove d == {0: 0, 1: 1, 2: 2, 3: 3}:

obj = cast(id(d), POINTER(PyDictObject)).contents # works!!` 

Ecco la mia traduzione dal 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 
] 
+0

Nota: è possibile eseguire il collegamento a [hg.python.org direttamente] (http://hg.python.org/cpython/file/3.3/Objects/dictobject.c#l72). Prova 'ctypes.CFUNCTYPE' a definito' dict_lookup_func'. – jfs

+0

UPDATE: ora ho dichiarato il tipo di dk_lookup usando CFUNCTYPE: – LeslieK

+0

@ J.F.Sebastian: Grazie. Ora ho dichiarato il tipo di dk_lookup usando CFUNCTYPE. Dk_entries sembra giusto? Il codice C utilizza dk_entries [1]. – LeslieK

risposta

3

Il mio problema era accedere alla struttura C sottostante un dizionario python implementato in Cpython 3.3. Ho iniziato con le strutture C fornite in cpython/Objects/dictobject.c e Include/dictobject.h. Tre strutture C sono coinvolte nella definizione del dizionario: PyDictObject, PyDictKeysObject e PyDictKeyEntry. La traduzione corretta di ogni struct in python è la seguente. I commenti indicano dove dovevo fare delle correzioni. Grazie a @eryksun per avermi guidato lungo la strada !!

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) 

La funzione seguente fornisce l'accesso al PyDictObject sottostante dizionario pitone:

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 

Se d è un dizionario pitone con coppie di valori-chiave, allora obj è l'istanza PyDictObject che contiene la chiave- coppie di valori:

obj = cast(id(d), POINTER(PyDictObject)).contents 

un'istanza del PyDictKeysObject è:

key_obj = obj.ma_keys.contents 

Un puntatore alla chiave memorizzata nello slot 0 del dizionario è:

key_obj.dk_entries[0].me_key 

Il programma che utilizza queste classi, insieme con le routine che analizzano le collisioni hash di ogni chiave inserita in un dizionario, si trova here. Il mio codice è una modifica del codice scritto da Brandon Rhodes per python 2.x. Il suo codice è here.

+0

@eryksun Sapevo di essere stato confuso da "un puntatore a un array, l'intero array". Quando sono tornato a cercare la mia confusione ho visto il tuo commento e lo apprezzo molto. Quindi, quando accediamo all'attributo dk_entries, restituiamo l'intero array. Perché la struct è definita per contenere l'intero array? [Commenti di Eli Bendersky] (http://eli.thegreenplace.net/2010/01/11/pointers-to-arrays-in-c/): Veramente, non riesco a immaginare perché si possa usare un puntatore a un array nella vita reale. – LeslieK

+0

Gli esempi di Eli mancano di un caso comune per usare 'int (* p) [4]', o in alternativa 'int p [] [4]', come parametro. Guarda solo '(* p) [2] = 10', ma C sa che' * p' è una matrice di 4 valori 'int', quindi' p [1] 'aggiunge' 4 * sizeof (int) 'all'indirizzo di base. Quindi ora possiamo gestirlo intuitivamente come un array 2D n x 4, ad es. 'p [1] [2] = 10'. C99 ci consente persino di passare il numero di colonne come argomento, ad es. 'void test (size_t n, size_t m, int p [] [m])'. – eryksun

+0

* "Perché la struct è definita per contenere l'intero array?" * Non l'ho studiato approfonditamente. Immagino che possa migliorare le prestazioni per i piccoli dicts; è assegnato in una chiamata in un blocco contiguo che migliora la localizzazione della cache. Ma avresti davvero bisogno di qualcuno che abbia più familiarità con il design ... – eryksun