'use ctypes.py_object to implement array class in python

This is first part of code.

from ctypes import py_object
from typing import TypeVar, Generic

T = TypeVar('T')


class ArrayR(Generic[T]):
    def __init__(self, length: int) -> None:
        """ Creates an array of references to objects of the given length
        :complexity: O(length) for best/worst case to initialise to None
        :pre: length > 0
        """
        if length <= 0:
            raise ValueError("Array length should be larger than 0.")
        self.array = (length * py_object)() # initialises the space
        self.array[:] =  [None for _ in range(length)]

    def __setitem__(self, index: int, value: T) -> None:
        """ Sets the object in position index to value
        :complexity: O(1)
        :pre: index in between 0 and length - self.array[] checks it
        """
        self.array[index] = value

I know self.array = (length * py_object)() is instantiating ctypes.py_object * size type. But how does self.array[:] = [None for _ in range(length)] work?

If you don't mind, can you explain what does

instantiating type

do in further detail?

Thanks.



Solution 1:[1]

int is a type. int() creates an instance of that type:

>>> int
<class 'int'>
>>> int()
0

pyobject * length is also a type, (pyobject * length)() creates an instance of that type:

>>> from ctypes import *
>>> py_object * 5
<class '__main__.py_object_Array_5'>
>>> (py_object * 5)()
<__main__.py_object_Array_5 object at 0x000002306F5C59C0>

But py_object wraps a C PyObject* and is initialized to C NULL:

>>> a = (py_object * 5)()
>>> a
<__main__.py_object_Array_5 object at 0x000002306FADACC0>
>>> a[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: PyObject is NULL

[None for _ in range(5)] creates a list of Python None objects. Using slice notation, the py_object array elements are assigned to wrap each None object:

>>> a[:] = [None for _ in range(5)]
>>> a
<__main__.py_object_Array_5 object at 0x000002306FADACC0>
>>> print(a[0])
None
>>> print(a[4])
None

Note without slice notation used to replace all elements in the existing list, a = [None for _ in range(5)] would just create a new Python list of Nones and it would not be a py_object array:

>>> a = [None for _ in range(5)]
>>> a
[None, None, None, None, None]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Tolonen