43 Stimmen

Abrufen von Daten aus ctypes Array in Numpy

Ich verwende ein Python (über ctypes ) gewickelte C-Bibliothek, um eine Reihe von Berechnungen durchzuführen. In verschiedenen Stadien der Ausführung möchte ich Daten in Python erhalten, und zwar numpy Arrays.

Das Wrapping, das ich verwende, bietet zwei verschiedene Rückgabemöglichkeiten für Array-Daten (was für mich von besonderem Interesse ist):

  • ctypes Array : Wenn ich es tue type(x) (wobei x die ctypes Array, erhalte ich eine <class 'module_name.wrapper_class_name.c_double_Array_12000'> im Gegenzug. Ich weiß, dass diese Daten eine Kopie der internen Daten aus der Dokumentation sind, und ich kann sie in eine numpy Array leicht:

    >>> np.ctypeslib.as_array(x)

Dies gibt eine 1D numpy Array der Daten.

  • ctype Zeiger auf Daten : In diesem Fall aus der Dokumentation der Bibliothek, verstehe ich, dass ich einen Zeiger auf die Daten gespeichert und verwendet direkt an die Bibliothek zu erhalten. Molke ich tun type(y) (wobei y der Zeiger ist) erhalte ich <class 'module_name.wrapper_class_name.LP_c_double'> . In diesem Fall bin ich immer noch in der Lage, die Daten wie folgt zu indizieren y[0][2] , aber ich war nur in der Lage, es in Numpy über eine super umständlich zu bekommen:

    >>> np.frombuffer(np.core.multiarray.int_asbuffer(
        ctypes.addressof(y.contents), array_length*np.dtype(float).itemsize))

Ich habe dies in einem alten numpy Mailingliste Thema von Travis Oliphant , aber nicht in der numpy Dokumentation. Wenn ich stattdessen wie oben beschrieben vorgehe, erhalte ich das folgende Ergebnis:

>>> np.ctypeslib.as_array(y)
...
...  BUNCH OF STACK INFORMATION
...
AttributeError: 'LP_c_double' object has no attribute '__array_interface__'

Ist das np.frombuffer die beste oder einzige Möglichkeit, dies zu tun? Ich bin offen für andere Vorschläge, aber ich würde gerne noch numpy da ich eine Menge anderer Nachbearbeitungscodes habe, die sich auf numpy Funktionalität, die ich mit diesen Daten verwenden möchte .

0 Stimmen

Haben Sie die Kontrolle über die C-Lib? Können Sie die API der Bibliothek ändern?

0 Stimmen

Ja - ich habe die Quelle. Ich bin mir nicht sicher, welchen Weg ich einschlagen soll, da der Pointer-Ansatz es Python ermöglicht, direkt auf die Daten zuzugreifen, was in einigen Fällen ein Vorteil sein könnte. In meinem Fall wäre es jedoch von Vorteil, wenn alles als Zeiger ausgegeben würde. ctype Array. Irgendwelche Empfehlungen?

2 Stimmen

Ich würde vorschlagen, dass die Bibliothek ein (NumPy-) Array verwendet, das Sie in Python zuweisen und an die Bibliothek weitergeben. Auf diese Weise können Sie auf denselben Speicher zugreifen, müssen sich aber nicht mit umständlichen Konvertierungen herumschlagen. Sie haben bereits ein NumPy-Array, und die Übergabe an eine Bibliothek wird gut unterstützt durch die Verwendung von numpy.ctypeslib.ndpointer als Argumenttyp für die ctypes-Verschalung Ihrer Funktion. (Wenn das nicht klar ist, fragen Sie einfach...)

31voto

Sven Marnach Punkte 525472

Das Erstellen von NumPy-Arrays aus einem ctypes-Zeigerobjekt ist eine problematische Operation. Es ist unklar, wem der Speicher, auf den der Zeiger zeigt, tatsächlich gehört. Wann wird er wieder freigegeben? Wie lange ist er gültig? Wann immer möglich, würde ich versuchen, diese Art von Konstrukt zu vermeiden. Es ist viel einfacher und sicherer, im Python-Code Arrays zu erstellen und sie an die C-Funktion zu übergeben, als Speicher zu verwenden, der von einer Python-feindlichen C-Funktion zugewiesen wurde. Indem Sie Letzteres tun, machen Sie in gewissem Maße die Vorteile einer Hochsprache zunichte, die sich um die Speicherverwaltung kümmert.

Wenn Sie wirklich sicher sind, dass sich jemand um den Speicher kümmert, können Sie ein Objekt erstellen, das das Python-"Pufferprotokoll" offenlegt, und dann ein NumPy-Array mit diesem Pufferobjekt erstellen. Sie haben in Ihrem Beitrag eine Möglichkeit zur Erstellung des Pufferobjekts angegeben, und zwar über das undokumentierte int_asbuffer() Funktion:

buffer = numpy.core.multiarray.int_asbuffer(
    ctypes.addressof(y.contents), 8*array_length)

(Anmerkung: Ich ersetzte 8 für np.dtype(float).itemsize . Es ist immer 8, egal auf welcher Plattform.) Eine andere Möglichkeit, das Pufferobjekt zu erstellen, wäre der Aufruf der Funktion PyBuffer_FromMemory() Funktion aus der Python C API über ctypes:

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object
buffer = buffer_from_memory(y, 8*array_length)

Für beide Möglichkeiten können Sie ein NumPy-Array aus buffer von

a = numpy.frombuffer(buffer, float)

(Ich verstehe eigentlich nicht, warum Sie den Begriff .astype() anstelle eines zweiten Parameters für frombuffer ; außerdem frage ich mich, warum Sie np.int während Sie zuvor sagten, dass das Array Folgendes enthält double s.)

Ich fürchte, es wird nicht viel einfacher als das, aber so schlimm ist es auch wieder nicht, oder? Sie könnten all die hässlichen Details in einer Wrapper-Funktion begraben und sich nicht mehr darum kümmern.

0 Stimmen

Das ist großartig - danke für den Überblick über die Vor- und Nachteile. .astype() Aufruf war nur ein versehentlicher Kopier- und Einfügefehler. Ich habe ihn jetzt aus meiner Frage herausgenommen. Danke, dass Sie das bemerkt haben.

2 Stimmen

Für Python3 können Sie Folgendes verwenden PyMemoryView_FromMemory pas PyBuffer_FromMemory . Viele Dinge, die früher Puffer genannt wurden, heißen jetzt Memoryviews.

0 Stimmen

numpy.core.multiarray.int_asbuffer hat unter Python 3 nie funktioniert und existiert nicht mehr

14voto

seeker Punkte 1096

Eine andere Möglichkeit (die möglicherweise neuere Versionen von Bibliotheken erfordert, als zum Zeitpunkt des Verfassens der ersten Antwort verfügbar waren): Ich habe etwas Ähnliches mit ctypes 1.1.0 y numpy 1.5.0b2 ) ist die Konvertierung vom Zeiger zum Array.

np.ctypeslib.as_array(
    (ctypes.c_double * array_length).from_address(ctypes.addressof(y.contents)))

Dies scheint immer noch die Semantik des gemeinsamen Eigentums zu haben, so dass Sie wahrscheinlich sicherstellen müssen, dass Sie den zugrunde liegenden Puffer schließlich freigeben.

2 Stimmen

Oder ohne spezielle Unterstützung von numpy: Sie könnten die y Zeiger auf einen Zeiger auf einen Array-Typ: ap = ctypes.cast(y, ctypes.POINTER(ArrayType)) wobei ArrayType = ctypes.c_double * array_length und erstellen Sie daraus ein Numpy-Array: a = np.frombuffer(ap.contents) . Siehe Wie konvertiert man einen Zeiger auf ein C-Array in ein Python-Array?

0 Stimmen

Ich habe dies versucht, aber das ap-Objekt hat kein Mitglied, keinen Inhalt.

0 Stimmen

@TotteKarlsson: der Code aus dem Link funktioniert so wie er ist (ich habe ihn getestet). Es ist wahrscheinlich ein Fehler in Ihrem Code (es kann auch ein Unterschied zwischen verschiedenen Python-Versionen sein, aber das ist weniger wahrscheinlich). Wenn Sie ihn nicht behoben haben; ein minimales, aber vollständiges Codebeispiel erstellen geben Sie Ihr Betriebssystem, die Python-Version und stellen Sie sie als neue SO-Frage

13voto

Eric Punkte 90800

np.ctypeslib.as_array ist alles, was Sie hier brauchen.

Aus einem Array:

 c_arr = (c_float * 8)()
 np.ctypeslib.as_array(c_arr)

Von einem Zeiger

 c_arr = (c_float * 8)()
 ptr = ctypes.pointer(c_arr[0])
 np.ctypeslib.as_array(ptr, shape=(8,))

0 Stimmen

Das hat bei mir perfekt funktioniert, danke. Ich übergebe eine Python ctypes.Structure mit Array-Zeigern an eine C-Funktion, die die Daten verarbeitet; dann lese ich den Inhalt des Arrays in Python zurück. Python 3.8, Numpy 1.19, ctypes 1.1.0

11voto

wordy Punkte 529

Beides hat bei mir in Python 3 nicht funktioniert. Als eine allgemeine Lösung für die Umwandlung eines ctypes Zeiger in ein Numpy ndarray in Python 2 und 3 fand ich dies funktioniert (über einen schreibgeschützten Puffer erhalten):

def make_nd_array(c_pointer, shape, dtype=np.float64, order='C', own_data=True):
    arr_size = np.prod(shape[:]) * np.dtype(dtype).itemsize 
    if sys.version_info.major >= 3:
        buf_from_mem = ctypes.pythonapi.PyMemoryView_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buf_from_mem.argtypes = (ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
        buffer = buf_from_mem(c_pointer, arr_size, 0x100)
    else:
        buf_from_mem = ctypes.pythonapi.PyBuffer_FromMemory
        buf_from_mem.restype = ctypes.py_object
        buffer = buf_from_mem(c_pointer, arr_size)
    arr = np.ndarray(tuple(shape[:]), dtype, buffer, order=order)
    if own_data and not arr.flags.owndata:
        return arr.copy()
    else:
        return arr

0 Stimmen

Das ist großartig! Array kommt gedreht heraus - komisch :)

5voto

Markus Dutschke Punkte 6917

Verwendung von np.ndarrays als ctypes Argumente

Der beste Ansatz ist die Verwendung von ndpointer wie in der numpy-docs .

Dieser Ansatz ist flexibler als die Verwendung von z. B., POINTER(c_double), da mehrere Einschränkungen angegeben werden können, die beim Aufruf der Funktion ctypes überprüft werden. Dazu gehören Datentyp Typ, Anzahl der Dimensionen, Form und Flags. Erfüllt ein gegebenes Array nicht die angegebenen Einschränkungen nicht erfüllt, wird ein TypeError ausgelöst.

Minimales, reproduzierbares Beispiel

Aufruf von memcpy von python. Schließlich der Dateiname der Standard-C-Bibliothek libc.so.6 angepasst werden muss.

import ctypes
import numpy as np

n_bytes_f64 = 8
nrows = 2
ncols = 5

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.memcpy.argtypes = [
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=2, flags='C_CONTIGUOUS'),
    np.ctypeslib.ndpointer(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS'),
    ctypes.c_size_t]
clib.memcpy.restype = ctypes.c_void_p

arr_from = np.arange(nrows * ncols).astype(np.float64)
arr_to = np.empty(shape=(nrows, ncols), dtype=np.float64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)

print('\ncalling clib.memcpy ...\n')
clib.memcpy(arr_to, arr_from, nrows * ncols * n_bytes_f64)

print('arr_from:', arr_from)
print('arr_to:', arr_to)

Ausgabe

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0.0e+000 4.9e-324 9.9e-324 1.5e-323 2.0e-323]
 [2.5e-323 3.0e-323 3.5e-323 4.0e-323 4.4e-323]]

calling clib.memcpy ...

arr_from: [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
arr_to: [[0. 1. 2. 3. 4.]
 [5. 6. 7. 8. 9.]]

Wenn Sie die ndim=1/2 Argumente von ndpointer nicht mit den Dimensionen von arr_from/arr_to scheitert der Code mit einer ArgumentError .

Da der Titel dieser Frage recht allgemein gehalten ist, ...

Aufbau einer np.ndarray von einer ctypes.c_void_p Ergebnis

Minimales, reproduzierbares Beispiel

In dem folgenden Beispiel wird ein Teil des Speichers durch malloc und mit 0en gefüllt durch memset . Dann wird ein Numpy-Array konstruiert, um auf diesen Speicher zuzugreifen. Natürlich gibt es dabei einige Probleme mit den Besitzverhältnissen, da Python Speicher, der in c alloziert wurde, nicht freigibt. Speicherlecks muss man kostenlos den zugewiesenen Speicher wieder durch ctypes. Die copia Methode kann für die np.ndarray zu erwerben Eigentum .

import ctypes
import numpy as np

n_bytes_int = 4
size = 7

clib = ctypes.cdll.LoadLibrary("libc.so.6")

clib.malloc.argtypes = [ctypes.c_size_t]
clib.malloc.restype = ctypes.c_void_p

clib.memset.argtypes = [
    ctypes.c_void_p,
    ctypes.c_int,
    ctypes.c_size_t]
clib.memset.restype = np.ctypeslib.ndpointer(
    dtype=np.int32, ndim=1, flags='C_CONTIGUOUS')

clib.free.argtypes = [ctypes.c_void_p]
clib.free.restype = ctypes.c_void_p

pntr = clib.malloc(size * n_bytes_int)
ndpntr = clib.memset(pntr, 0, size * n_bytes_int)
print(type(ndpntr))
ctypes_pntr = ctypes.cast(ndpntr, ctypes.POINTER(ctypes.c_int))
print(type(ctypes_pntr))
print()
arr_noowner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,))
arr_owner = np.ctypeslib.as_array(ctypes_pntr, shape=(size,)).copy()
# arr_owner = arr_noowner.copy()

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\nfree allocated memory again ...\n')
_ = clib.free(pntr)

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

print('\njust for fun: free some python-memory ...\n')
_ = clib.free(arr_owner.ctypes.data_as(ctypes.c_void_p))

print('arr_noowner (at {:}): {:}'.format(arr_noowner.ctypes.data, arr_noowner))
print('arr_owner (at {:}): {:}'.format(arr_owner.ctypes.data, arr_owner))

Ausgabe

<class 'numpy.ctypeslib.ndpointer_<i4_1d_C_CONTIGUOUS'>
<class '__main__.LP_c_int'>

arr_noowner (at 104719884831376): [0 0 0 0 0 0 0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

free allocated memory again ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [0 0 0 0 0 0 0]

just for fun: free some python-memory ...

arr_noowner (at 104719884831376): [ -7687536     24381 -28516336     24381         0         0         0]
arr_owner (at 104719884827744): [ -7779696     24381 -28516336     24381         0         0         0]

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X