[plum.array] Tutorial: Creating and Using Array Types

The Array type facilitates a series of uniformly typed variables within a bytes buffer using Python list behaviors. The following tutorials demonstrate the creating and using plum array types.

Creating an Array Type

Without further customization, Array elements are UInt8 and during byte unpacking greedily consume bytes (or produces bytes) until exhaustion:

>>> from plum.array import Array
>>>
>>> # packs as many elements in a list as provided
>>> Array.pack([1, 2, 3])
bytearray(b'\x01\x02\x03')
>>>
>>> # unpacks (consumes) as many bytes as provided
>>> Array.unpack(b'\x01\x02\x03')
[1, 2, 3]

Subclassing the Array type offers the ability to control the type of the items in the array as well as the array dimensions. Specify array dimensions as a tuple with the dims argument. Use the item_cls argument to control the type used to pack or unpack array elements. Any fixed size plum type may be used. For example:

>>> from plum.int.big import UInt16
>>>
>>> class Array2x2(Array, dims=(2, 2), item_cls=UInt16):
...      pass
...
>>> Array2x2.pack([[1, 2], [3, 4]])
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')
>>>
>>> Array2x2.unpack(b'\x00\x01\x00\x02\x00\x03\x00\x04')
[[1, 2], [3, 4]]

Unpacking Bytes

plum array types convert bytes into lists of elements where each element is unpacked per the item_cls specified when subclassing Array. For multidimensional arrays, the unpacked element bytes are organized within nested lists that follow the dimensions. Array types support the various plum unpacking mechanisms. For example (using the two dimensional array subclass from the previous section):

>>> from plum import unpack, Buffer
>>>
>>> # utility function
>>> unpack(Array2x2, b'\x00\x01\x00\x02\x00\x03\x00\x04')
[[1, 2], [3, 4]]
>>>
>>> # class method
>>> Array2x2.unpack(b'\x00\x01\x00\x02\x00\x03\x00\x04')
[[1, 2], [3, 4]]
>>>
>>> # bytes buffer
>>> with Buffer(b'\x00\x01\x00\x02\x00\x03\x00\x04') as buffer:
...     a = buffer.unpack(Array2x2)
...
>>> a
[[1, 2], [3, 4]]

Undimensioned array types (no dims argument specified) consume all remaining bytes and produce a one dimensional list of unpacked elements:

>>> from plum.int.big import UInt16
>>>
>>> class GreedyArray(Array, item_cls=UInt16):
...      """Consumes all bytes when unpacking."""
...
>>> GreedyArray.unpack(b'\x00\x01\x00\x02\x00\x03\x00\x04')
[1, 2, 3, 4]

Packing Bytes

plum array types convert a list of elements into bytes where each element is converted into bytes per the item_cls specified when subclassing Array. The list of elements must match the dimensions specified when the array type was created. For multidimensional arrays, the elements are expected to be organized with nested lists that follow the dimensions. Array types support the various plum packing mechanisms. For example (using the two dimensional array subclass from the previous section):

>>> from plum import pack
>>>
>>> # utility function
>>> pack(Array2x2, [[1, 2], [3, 4]])
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')
>>>
>>> # class method
>>> Array2x2.pack([[1, 2], [3, 4]])
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')
>>>
>>> # instance method
>>> Array2x2([[1, 2], [3, 4]]).pack()
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')

Undimensioned array types (no dims argument specified) produce bytes for all elements specified in a list:

>>> from plum.int.big import UInt16
>>>
>>> class GreedyArray(Array, item_cls=UInt16):
...      """Consumes all bytes when unpacking."""
...
>>> GreedyArray.pack([1, 2, 3, 4])
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')

Instantiation and Properties

Array types have list behaviors. The constructor accepts an iterable of elements. For multidimensional arrays, nested iterables must be provided that match the array dimensions. The constructor verifies the iterables have the proper dimensions. For example (using the two dimensional array subclass from the previous section):

>>> from plum.int.big import UInt16
>>>
>>> class Array2x2(Array, dims=(2, 2), item_cls=UInt16):
...      pass
...
>>> array = Array2x2([[1, 2], [3, 4]])
>>>
>>> # instance representation includes class name
Array2x2([[1, 2], [3, 4]])
>>>
>>> # instances have list methods (e.g. append(), clear(), count(), etc.)
>>> array.count([1, 2])
1
>>>
>>> # instances support len() function and normal indexing
>>> len(array)
2
>>> array[0][0]
1
>>> array[1]  # class name in representation becomes generic for nested list
[3, 4]
>>>
>>> # instances have plum methods/properties (e.g. pack(), nbytes, etc.)
>>> array.nbytes
8
>>> array.pack()
bytearray(b'\x00\x01\x00\x02\x00\x03\x00\x04')

Tip

When constructing lists of elements for packing, consider instantiating an array type when the packing operation is not immediately performed. This way, element lists with improper dimensions get reported immediately rather than delayed until the packing operation.

The “Access” column produced by the dump property provides index information needed to access individual elemements:

>>> # access column in dump gives indication of
>>> array.dump()
+--------+--------+-------+-------+----------+
| Offset | Access | Value | Bytes | Type     |
+--------+--------+-------+-------+----------+
|        |        |       |       | Array2x2 |
|        | [0]    |       |       |          |
| 0      |   [0]  | 1     | 00 01 | UInt16   |
| 2      |   [1]  | 2     | 00 02 | UInt16   |
|        | [1]    |       |       |          |
| 4      |   [0]  | 3     | 00 03 | UInt16   |
| 6      |   [1]  | 4     | 00 04 | UInt16   |
+--------+--------+-------+-------+----------+

Sized Arrays in Structures

See the Sized Array Structure Tutorial for information on creating structures with an array member and a second member for controlling its size (dimensions).