3

I have a C Extension that receives and accepts PyCapsule objects.

In my python wrapper, how can I check if a python object is of the PyCapsule type object?

>>> # My C extension
>>> foo = Foo()
>>> capsule = foo.to_capsule()  # returns a PyCapsule object from the C extension
>>> capsule
<capsule object "foo" at 0xf707df08>
>>> type(capsule)
<class 'PyCapsule'>
isinstance(capsule, PyCapsule)
NameError: name 'PyCapsule' is not defined

What I'm trying to do is write a function like:

def push_capsule(capsule):
    # check that the `capsule` is of type PyCapsule
    # c_extension.push_capsule(capsule)
Matthew Moisen
  • 16,701
  • 27
  • 128
  • 231
  • You need to get the class object in scope somehow, either by importing it, or here, you can simply do `PyCapsule = type(capsule)` then use `isinstance(whatever, PyCapsule)` – juanpa.arrivillaga Feb 19 '20 at 22:34
  • @juanpa.arrivillaga I'm looking for the way to import it; what I would like to do is have a function check that the user passed in a PyCapsule, without having to instantiate a PyCapsule and extracting the type out. I've updated my OP to include that. – Matthew Moisen Feb 19 '20 at 23:06
  • It may not be exposed as an import anywhere, unfortunately. But what harm is there in instantiating a PyCapsule once in the global scope simply to get it's type? – juanpa.arrivillaga Feb 19 '20 at 23:07
  • @juanpa.arrivillaga If it's not exposed as an import anywhere, I think I might have to do that. – Matthew Moisen Feb 19 '20 at 23:08
  • Well, that sounds like a solution. It isn't particularly bad, although it is a bit inelegant I suppose. – juanpa.arrivillaga Feb 19 '20 at 23:08

3 Answers3

6

The PyCapsule type is not directly accessible from Python as you have noted. To detect it without the heavy ctypes dependency (in the accepted answer by DavidW), I would do the following instead:

def is_capsule(o):
    t = type(o)
    return t.__module__ == 'builtins' and t.__name__ == 'PyCapsule'
Wenzel Jakob
  • 555
  • 6
  • 14
2

It's a bit of a mess but you can get it from ctypes:

def get_capsule_type():
    class PyTypeObject(ctypes.Structure):
        pass  # don't need to define the full structure
    capsuletype = PyTypeObject.in_dll(ctypes.pythonapi, "PyCapsule_Type")
    capsuletypepointer = ctypes.pointer(capsuletype)
    return ctypes.py_object.from_address(ctypes.addressof(capsulepointerpointer)).value

Creating a py_object from an address looks to require an address which contains a PyObject*, rather than the PyObject* itself, hence the extra layer of indirection.

DavidW
  • 29,336
  • 6
  • 55
  • 86
1

In general, first check if your API offers some way to access the class you want to reference.

If it does not, recover the class from a dummy instance.

PyCapsule = type(Foo().to_capsule())

...

if isinstance(bar, PyCapsule):
    ...
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73