2014-04-27 12 views
5

Próbuję zmodyfikować kod Brandona Rhodesa Routines that examine the internals of a CPython dictionary, aby działał dla wersji CPython 3.3.python 3.3 dict: jak przekonwertować strukturę PyDictKeysObject na Pythona?

Wierzę, że udało mi się przetłumaczyć tę strukturę.

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

myślę dodaje dobrze wygląda teraz:

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

Ta linia kodu działa teraz, gdzie d == {0: 0, 1: 1, 2: 2, 3: 3}:

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

Oto moje tłumaczenie z 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

Uwaga: możesz utworzyć link do [hg.python.org bezpośrednio] (http://hg.python.org/cpython/file/3.3/Objects/dictobject.c#l72). Wypróbuj 'ctypes.CFUNCTYPE' dla zdefiniowanego' dict_lookup_func'. – jfs

+0

AKTUALIZACJA: Teraz zadeklarowałem typ dk_lookup przy użyciu CFUNCTYPE: – LeslieK

+0

@ J.F.Sebastian: Dziękuję. Teraz zadeklarowałem typ dk_lookup przy użyciu CFUNCTYPE. Czy dk_entries wygląda dobrze? Kod C używa dk_entries [1]. – LeslieK

Odpowiedz

3

Moim problemem było uzyskanie dostępu do struktury C leżącej u podstaw słownika Pythona zaimplementowanego w Cpythonie 3.3. Zacząłem od struktur C podanych w cpython/Objects/dictobject.c i Include/dictobject.h. W definiowaniu słownika są zaangażowane trzy C-struktury: PyDictObject, PyDictKeysObject i PyDictKeyEntry. Prawidłowe tłumaczenie każdej struktury C do Pythona jest następujące. Komentarze wskazują, gdzie muszę wprowadzić poprawki. Dziękuję @urenksun za prowadzenie mnie po drodze !!

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) 

następująca funkcja zapewnia dostęp do PyDictObject leżące u podstaw słowniku pytona:

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 

Jeśli d jest słownik pyton parami klucz wartość, to obj jest wystąpienie PyDictObject zawierający kluczykowy par wartości:

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

instancja PyDictKeysObject jest:

key_obj = obj.ma_keys.contents 

Wskaźnik do klucza przechowywanego w gnieździe 0 słownika to:

key_obj.dk_entries[0].me_key 

Program, który korzysta z tych klas, wraz z procedur, które sondy kolizji hash każdego klawisza wprowadzonego do słownika, znajduje się here. Mój kod jest modyfikacją kodu napisanego przez Brandona Rhodesa dla Pythona 2.x. Jego kod to here.

+0

@eryksun Wiedziałem, że jestem zdezorientowany przez "wskaźnik do tablicy, cała tablica". Kiedy wróciłem, by zbadać moje zamieszanie, zobaczyłem twój komentarz i bardzo to doceniam. Kiedy więc uzyskamy dostęp do atrybutu dk_entries, zwracamy całą tablicę. Dlaczego struktura jest zdefiniowana tak, aby zawierała całą tablicę? [Komentarze Eli Bendersky] (http://eli.thegreenplace.net/2010/01/11/pointers-to-arrays-in-c/): Naprawdę, nie mogę sobie wyobrazić, dlaczego można by użyć wskaźnika do tablicy w prawdziwym życiu. – LeslieK

+0

W przykładach Eli brakuje typowego przypadku użycia 'int (* p) [4]', lub alternatywnie 'int p [] [4]', jako parametru. Patrzy tylko na '(* p) [2] = 10', ale C wie, że' * p' jest tablicą 4 wartości 'int', więc' p [1] 'dodaje' 4 * sizeof (int) 'do adresu bazowego. Teraz możemy intuicyjnie obsługiwać to jako tablicę 2D n 4, np. 'p [1] [2] = 10'. C99 pozwala nawet przekazać liczbę kolumn jako argument, np. 'void test (size_t n, size_t m, int p [] [m])'. – eryksun

+0

* "Dlaczego struktura jest zdefiniowana tak, aby zawierała całą tablicę?" * Nie zbadałem tego dogłębnie. Sądzę, że może poprawić wydajność małych dyktatur; jest przydzielany w jednym wywołaniu w sąsiadującym bloku, który poprawia lokalizację pamięci podręcznej. Ale naprawdę musiałbyś poprosić kogoś bardziej zaznajomionego z projektem ... – eryksun