3.2.2.4. Struct-Like Objects#

The _StructLike protocol can be used to emulate struct types. Even though, pack() and unpack() allow so-called partial struct-like objects, there won’t be a conversion within struct class definitions. It is always recommended to implement all methods conforming to the _StructLike protocol.

Special Methods for Struct-Like objects#

object.__pack__(self, obj, context) None#

Invoked to serialize the given object into an output stream, __pack__() is designed to implement the behavior necessary for packing a collection of elements or a single element. Accordingly, the input obj may be an Iterable or a singular element.

The absence of a standardized implementation for deserializing a collection of elements is deliberate. For example, all instances of the PyStructFormattedField utilize the Python library struct internally to pack and unpack data. To optimize execution times, a collection of elements is packed and unpacked in a single call, rather than handling each element individually.

The context must incorporate specific members, mentioned in Context. Any data input verification is implemented by the corresponding class.

__pack__() is invoked by the pack() method defined within this library. Its purpose is to dictate how input objects are written to the stream. It is crucial to note that the outcome of this function is ignored.

Changed in version beta: The stream parameter has been removed and was instead moved into the context.

object.__unpack__(self, context)#

Called to desersialize objects from an input stream (the stream is stored in the given context). The result of __unpack__() is not going to be ignored.

Every implementation is tasked with the decision of whether to support the deserialization of multiple elements concurrently. By default, the Field class stores all essential attributes required to determine the length of elements set for unpacking. The __unpack__() method is activated through the unpack() operation, integrated with the default struct classes — namely, Sequence, Struct, and Field.

Changed in version beta: The stream parameter has been removed and was instead moved into the context.

object.__size__(self, context)#

This method serves the purpose of determining the space occupied by this struct, expressed in bytes. The availability of a context enables the execution of a _ContextLambda, offering support for dynamically sized structs. Furthermore, for the explicit definition of dynamic structs, the option to raise a DynamicSizeError is provided.

object.__type__(self)#

The configuration of Structs incorporates type replacement before a dataclass is created. This feature was specifically introduced for documentation purposes. The optional __type__() method allows for the specification of a type, with the default being Any if not explicitly defined.

Note

The implementation of the __type__() method is optional and, therefore, not mandatory as per the library’s specifications.

The following example demonstrates the use of the sphinx-autodoc extension to document struct classes with the S_REPLACE_TYPE option enabled. Only documented members are displayed.

.. autoclass:: examples.formats.nibarchive.NIBHeader()
    :members:

Will be displayed as:

class NIBHeader#

Example class doc comment

NIBHeader.magic: bytes#

example field doc comment

NIBHeader.unknown_1: int#

second field doc comment

In this illustration, the extra parentheses at the end are included to prevent the automatic creation of constructors.

Struct containers#

class.__struct__#

All models annotated with either @struct or @bitfield fall into the category of struct containers. These containers store the additional class attribute __struct__().

Internally, any types utilizing this attribute can be employed within a struct, bitfield, or sequence definition. The type of the stored value must be conforming to the _StructLike protocol.

Template Containers#

class.__template__#

All template classes store information about the used template type variables. Whether they are required or just positional. In addition, default inferred types are stored as well.

Protocols for Struct-like objects#

To represent a _StructLike object, all previously described methods must be implemented:

class _StructLike[_IT, _OT]#
__pack__(self, obj: _IT, context: _ContextLike) None#
__unpack__(self, context: _ContextLike) _OT#
__size__(self, context: _ContextLike) int#
class _ContainsStruct[_IT, _OT]#
__struct__: _StructLike[_IT, _OT]#
class _SupportsPack[_IT]#
__pack__(self, obj: _IT, context: _ContextLike) None#
class _SupportsUnpack[_OT]#
__unpack__(self, context: _ContextLike) _OT#
class _SupportsSize#
__size__(self, context: _ContextLike) int#
class _SupportsType#
__type__(self) type | str | None#