Unfortunately, sometimes you have to get Python to interface with C code, and then you’re pretty much seeing pointers everywhere you look. The following code is a study on pointer manipulation in Python using the ctypes library.
Specifically, I was interested in how to move a pointer along an array (or buffer). It seems that pointer manipulation can only be done using a void pointer within Python. I did see other hacks such as using pointers to pointers within Python just to allow access to an editable address, but the technique below is the cleanest option I found. If you have other suggestions, please feel free to add them in the comments below.
Good luck.
Python 3.7.7 (default, Mar 10 2020, 15:43:27)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> # First create a class that defines the type of the array we'd like to
>>> # create
>>> x_t = ctypes.c_short * 10
>>> # Now we can actually create an instance of the array
>>> x = x_t()
<__main__.c_short_Array_10 object at 0x10a7c4b00>
>>> # Let's define a function to print the contents of the array
>>> def printContents(array):
... for i in array:
... print(i, end=" ")
... print("") # New line
>>> # And call our function
>>> printContents(x)
0 0 0 0 0 0 0 0 0 0
>>> # We can populate the array with some data
>>> x[0] = 99
>>> x[1] = -1
>>> x[2] = 42
>>> # Print out the array
>>> printContents(x)
99 -1 42 0 0 0 0 0 0 0
>>> # Get a pointer to the array
>>> p = ctypes.pointer(x)
>>> # What's the pointer's type?
>>> type(p)
<class '__main__.LP_c_short_Array_10'>
>>> # We can cast to a pointer-to-c_short (the base type of our array)
>>> p2 = ctypes.cast(p, ctypes.POINTER(ctypes.c_short))
>>> # And we can use it to view the first value
>>> print(p2.contents)
c_short(99)
>>> # We seem only to be able to do pointer arithmetic with void pointers
>>> # (c_void_p), so let's cast to get a pointer to the first value of the
>>> # array and then offset it by two bytes to get the second element
>>> oldAddress = ctypes.addressof(p2.contents)
>>> newAddress = oldAddress + 2 # because c_shorts are two bytes long
>>> p3 = ctypes.c_void_p(newAddress)
>>> # Let's view their values
>>> oldAddress
4470827792
>>> newAddress
4470827794
c_void_p(4470827794)
>>> # Unfortunately, you can't read the contents of a void pointer:
>>> p3.contents
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'c_void_p' object has no attribute 'contents'
>>> # But you can cast the void pointer back to the desired type:
>>> p4 = ctypes.cast(p3, ctypes.POINTER(ctypes.c_short))
>>> p4.contents
c_short(-1)
>>> # Now, using only the void pointer, can we increment it such that it
>>> # moves to the next element of the array? Yes, but this time we have
>>> # to use the value member:
>>> oldAddress = p3.value
>>> newAddress = oldAddress + 2 # again, because shorts are two bytes
>>> p3 = ctypes.c_void_p(newAddress)
>>> # Let's view their values
>>> oldAddress
4470827794
>>> newAddress
4470827796
c_void_p(4470827796)
>>> # And finally, what is the value of the element-being-pointed-to?
>>> p5 = ctypes.cast(p3, ctypes.POINTER(ctypes.c_short))
>>> p5.contents
c_short(42)
>>> # Isn't the definition of p5 the same as p4? Yes, but clearly a copy
>>> # of the pointer is made during the cast:
>>> p4.contents
c_short(-1)