pythoncpython-3.xctypespyobject

How does ctypes.c_int differs from ctypes.py_object?


I understand that multiplying a ctype with an integer is declaring an array.

For example, ctypes.c_int * 4 is an array of 4 integers.

Then what is py_object?

Please help me understand how does ctypes.c_int differs from ctypes.py_object?


Solution

  • from ctypes import c_int, py_object
    
    # Array of (three) integers initialized such that
    # a[0] = 1, a[1] = 2, a[2] = 3. 
    a = (c_int * 3)(1, 2, 3) 
    
    # Array of (three) arbitrary Python objects initialized
    # such that a[0] = "Pizza", a[1] = 5, a[2] = 5.813.
    b = (py_object * 3)("Pizza", 5, 5.813)
    
    print(a[:]) # -> [1, 2, 3]
    print(b[:]) # -> ['Pizza', 5, 5.813] 
    

    Trying to execute

    a = (c_int * 3)("Pizza", 5, 5.813)
    

    will generate a TypeError: an integer is required (got type str).

    This is because we have specified that we want an array of three elements, all of which are integers, but we have attempted to include a string inside the array as well.

    Arrays, unlike lists, require that all elements a[0], a[1], ... , a[n] are of the same type.

    By specifying py_object instead of c_int, you are essentially permitted to have an array of arbitrary Python objects, as opposed to only integers.

    Strictly speaking (in CPython), internally, the py_object way gives you an array of pointers to the PyObject datatype. Internally, all object types are extensions of PyObject.

    So the type of all the elements of the b array is pointer to PyObject. A pointer is nothing more than a memory address. So every element of the b array is just a memory address pointing to some arbitrary object in Python. Internally, integer, floats, lists, dictionaries, and so on are all PyObjects so an array with elements of type py_object allows for its elements to be memory addresses that refer to essentially any kind of object in Python (lists, tuples, integers, floats, and so on).

    When we print b[0], for example, Python gives us 'Pizza' as output. Internally, Python uses the memory address of the string object and returns the string for us.

    Pointers / memory addresses are used frequently in C and in the CPython source code. Python essentially hides memory addresses from us as it is a high-level language, abstracting away from low-level concepts such as pointers.

    I hope this helps!