2015-08-20 19 views
10

Ispirato da un altro answer qui, ho una funzione ctypes che io chiamo utilizzando ctypeslib.ndpointer:Come posso passare null a una libreria esterna, usando i ctypes, con un argomento dichiarato con ctypeslib.ndpointer?

lib.foo.argtypes = [ctypeslib.ndpointer(np.complex64, ndim=1, flags='C'), POINTER(c_int)] 

La funzione esterna è dichiarata in questo modo:

void foo(cmplx_float *array, int *length) 

mio problema è che io voglio chiamare la funzione due volte. La prima volta voglio passare nullptr all'argomento array, in modo che possa trovare la lunghezza richiesta. Poi la seconda volta, passerò in una serie numpy.

Così lo faccio in questo modo:

lib.foo(None, length) 

Questo non riesce con il seguente errore:

ctypes.ArgumentError: argument 1: : argument must be an ndarray

E 'possibile per me di passare nullptr?

+0

Sono un po' arrugginito su 'ctypes', ma sembra che se tu [crea il puntatore senza argomenti] (https://docs.python.org/2/library/ctypes.html#pointers), dovrebbe creare un puntatore nullo. –

+0

Probabilmente l'opzione più pulita è quella di sottoclasse del tipo restituito da 'ctypeslib.ndpointer', che a sua volta è una sottoclasse di [' ctypeslib._ndptr'] (https://github.com/numpy/numpy/blob/v1.9.2 /numpy/ctypeslib.py#L150). Sovrascrivi il suo metodo di classe 'from_param' per restituire' obj' se è 'None', altrimenti restituisce il risultato della chiamata al metodo' from_param' del genitore. – eryksun

+0

In alternativa è possibile modificare temporaneamente 'argtypes', ma non mi piace perché non è thread-safe. È anche possibile creare una seconda istanza del puntatore a funzione che utilizza 'argtypes = [c_void_p, POINTER (c_int)]', ma che richiede due puntatori di funzione è maldestro. – eryksun

risposta

4

Se (come me) avete a che fare con più tipi di array diversi, una soluzione un po 'più bello è quello di avvolgere ndpointer stessa:

import numpy as np 
from numpy.ctypeslib import ndpointer 

def wrapped_ndptr(*args, **kwargs): 
    base = ndpointer(*args, **kwargs) 
    def from_param(cls, obj): 
    if obj is None: 
     return obj 
    return base.from_param(obj) 
    return type(base.__name__, (base,), {'from_param': classmethod(from_param)}) 

ComplexArrayType = wrapped_ndptr(dtype=np.complex128, ndim=1, flags='C_CONTIGUOUS') 
DoubleArrayType = wrapped_ndptr(dtype=np.float64, ndim=1, flags='C_CONTIGUOUS') 
+0

Sembra piuttosto lucido. Lo proverò perché in effetti sto facendo esattamente lo stesso di te! –

1

Seguendo l'eccellente consiglio di eryksun in un commento ho suddiviso il tipo restituito da ctypeslib.ndpointer e implementato un from_param sottoposto a override. Questo è un po 'complicato in quanto deve essere fatto usando tecniche di classe factory, perché la classe restituita da ctypeslib.ndpointer è fatta da una fabbrica.

Il codice che ho finito con:

_ComplexArrayTypeBase = numpy.ctypeslib.ndpointer(dtype=numpy.complex128, ndim=1, 
    flags='C_CONTIGUOUS') 

def _from_param(cls, obj): 
    if obj is None: 
     return obj 
    return _ComplexArrayTypeBase.from_param(obj) 

ComplexArrayType = type(
    'ComplexArrayType', 
    (_ComplexArrayTypeBase,), 
    {'from_param': classmethod(_from_param)} 
) 

Molte grazie per eryksun per la sua (come sempre) la consulenza di esperti.

+0

Questo modello di chiamata a due passaggi è così comune che "_ndptr.from_param' dovrebbe già gestirlo. Considera di suggerire questa piccola modifica agli sviluppatori di NumPy. – eryksun

+0

@eryksun done: https://github.com/numpy/numpy/issues/6239 –