[plum.structure] Tutorial: Create Custom Structure Type¶
This tutorial shows how to create custom structure subclasses that define the name, type, default value, and significance for each member within a structure. (The significance of a member determines whether equality comparisons of the structure consider the member.) Predefining the structure members facilitates packing and unpacking the structure’s bytes and ensures instantiations include all necessary member values.
Member Type¶
To define a custom structure, use a traditional class definition and subclass
Structure
. Within the namespace of the subclass, use the Member
class
to specify the member name and member type. As shown, type annotations may be
used, but are optional. Within Structure
classes, the member definitions
control how bytes are packed and unpacked when using the structure type:
>>> from plum import pack, unpack
>>> from plum.structure import Member, Structure
>>> from plum.int.little import UInt8, UInt16
>>>
>>> class MyStruct(Structure):
... m1: int = Member(cls=UInt8)
... m2: int = Member(cls=UInt16)
...
>>> pack(MyStruct, {'m1': 1, 'm2': 2})
bytearray(b'\x01\x02\x00')
>>>
>>> unpack(MyStruct, b'\x01\x02\x00')
MyStruct(m1=1, m2=2)
The custom structure class uses the member definitions to ensure proper values are provided during instantiation:
>>> MyStruct(m1=1)
Traceback (most recent call last):
...
TypeError: __init__() missing 1 required positional argument: 'm2'
>>>
>>> MyStruct(m1=1, m2=2, m3=3)
Traceback (most recent call last):
...
TypeError: __init__() got an unexpected keyword argument 'm3'
Member Default¶
Use the default
argument in the member definition to specify a default
value for the member. Instantiations and pack()
operations use the
default value for the member when one was not provided:
>>> from plum import pack
>>> from plum.structure import Member, Structure
>>> from plum.int.little import UInt8, UInt16
>>>
>>> class MyStruct(Structure):
... m1: int = Member(cls=UInt8)
... m2: int = Member(cls=UInt16, default=2)
...
>>> pack(MyStruct, {'m1': 1})
bytearray(b'\x01\x02\x00')
>>>
>>> MyStruct(m1=1)
MyStruct(m1=1, m2=2)
Tip
For equality comparisons, compare against an instance of the class
instead of a traditional list
and skip members with defaults
where the expected value is the default:
>>> # dict compare fails (requires all members, and all members match)
>>> unpack(MyStruct, b'\x01\x02\x00') == [1]
False
>>>
>>> # compare with structure instance instead
>>> unpack(MyStruct, b'\x01\x02\x00') == MyStruct(m1=1)
True
Ignore Member¶
To ignore certain members when evaluating equality of a structure, use
the ignore
argument in the member definition:
>>> from plum import pack
>>> from plum.structure import Member, Structure
>>> from plum.int.little import UInt8
>>>
>>> class MyStruct(Structure):
... m1: int = Member(cls=UInt8)
... m2: int = Member(cls=UInt8)
... m3: int = Member(cls=UInt8, default=0, ignore=True)
...
>>> x = unpack(MyStruct, b'\x01\x02\x03')
>>>
>>> # normal dict requires all members be consistent (presence and value)
>>> x.asdict() == {'m1': 1, 'm2': 2}
False
>>>
>>> # structures allow ignored members to be absent or mismatched
>>> x == MyStruct(m1=1, m2=2) # m3 absent
True
>>> x == MyStruct(m1=1, m2=2, m3=99) # m3 mismatched
True