QuickStruct is a small library written in Python that allows you to easily create C structs (and a bit more dynamic stuff) in Python!
pip install quickstruct
It's fairly easy to use
from quickstruct import *
class Person(DataStruct):
name: String
age: i8
Structs can also be composed
class TeachingClass(DataStruct):
teacher: Person
# We use Array[T] to make it dynamic sized
students: Array[Person]
And structs can also inherit other structs (we even support multiple inheritance!)
class Employee(Person):
salary: i32
Now let's use the structs we defined
# We have 2 options when initializing.
# Either by setting each attribute individually
person = Person()
person.name = "John Doe"
person.age = 42
# Or by passing them as keyword arguments
person = Person(name="John Doe", age=42)
The main use for C structs is to convert them from bytes and back
data = person.to_bytes()
# Do something with the data
# And it's also easy to deserialize
person = Person.from_bytes(data)
When deserializing a struct with multiple bases or if one of the fields was overriden, the deserialization must be done through the exact type of the struct.
A fixed size struct is any struct that has a known fixed size at build time that doesn't depend on the data it holds. QuickStruct can verify a struct has a fixed size.
# The StructFlags.FixedSize flag is used to verify the struct has a fixed size.
# If the size could not be verified, a SizeError is raised.
class FixedSizeStruct(DataStruct, flags=StructFlags.FixedSize):
a: i32
b: i8
c: f32
d: char
e: String[10] # 10 character string
f: Person[3] # 3 'person' objects
# g: Array[i32] <- not a fixed size field. this will error
It is possible to query some information about a structure.
from quickstruct import *
class Fixed(DataStruct):
s: String[10]
x: i32
class Dynamic(DataStruct):
s: String
x: i32
print("Fixed.size:", Fixed.size) # 16 (padding automatically added)
print("Dynamic.size:", Dynamic.size) # -1 (dynamic size)
print("Fixed.is_fixed_size:", Fixed.is_fixed_size) # True
print("Dynamic.is_fixed_size:", Dynamic.is_fixed_size) # False
print("Fixed.is_dynamic_size:", Fixed.is_dynamic_size) # False
print("Dynamic.is_dynamic_size:", Dynamic.is_dynamic_size) # True
print("Fixed.fields:", Fixed.fields) # [s: String[10], __pad_0__: Padding(2), x: i32]
print("Dynamic.fields:", Dynamic.fields) # [s: String, x: i32]
print("Fixed.alignment:", Fixed.alignment) # 16.
print("Dynamic.alignment:", Dynamic.alignment) # -1 (no alignment because dynamic struct can't be aligned).
print("Fixed.is_final:", Fixed.is_final) # False
print("Dynamic.is_final:", Dynamic.is_final) # False
print("Fixed.is_protected:", Fixed.is_protected) # False
print("Dynamic.is_protected:", Dynamic.is_protected) # False
It is also possible to add padding to the struct. There are 2 ways to do that:
This can be done with the Padding
type.
class AlignedStruct(DataStruct):
c1: char
# This adds a single byte padding
_pad0: Padding
short: i16
# We can also add multi-byte padding
# Here we'll pad to get 8 byte alignment (missing 4 bytes)
_pad1: Padding[4]
This can done by passing some flags to the class definition. By default the struct is automatically aligned.
# Aligned on 2 byte boundary
class AlignedStruct(DataStruct, flags = StructFlags.Align2Bytes):
c1: char
# Padding will be added here
short: i16
Flag | Description |
---|---|
Default | The default to use if no flags are given. Same as AllowOverride | AlignAuto . |
NoAlignment | This is the most packed form of the struct. All fields are adjacent with no padding (unless manually added) |
Packed | Same as NoAlignment except that NoAlignment is a bit more optimized because no alignment is done. |
Align1Byte | Same as Packed |
Align2Bytes | Aligns the fields on 2 byte boundary. |
Align4Bytes | Aligns the fields on 4 byte boundary. |
Align8Bytes | Aligns the fields on 8 byte boundary. |
AlignAuto | Aligns the fields by their type. |
ReorderFields | Specifies the fields should be reordered in order to make the struct a little more compressed. |
ForceDataOnly | Deprecated. Specifies that the struct may only contain serializable fields. Data-only structs may only inherit data-only structs. |
AllowOverride | If set, fields defined in the struct may override fields that are defined in the base struct. |
TypeSafeOverride | If set, when fields are overridden, they must have the same type (which would make it pretty useless to override). Implies AllowOverride . If fields have a different type, an UnsafeOverrideError is raised. |
ForceSafeOverride | Deprectaed. Same as TypeSafeOverride . |
FixedSize | If set, the struct must have a fixed size. If not, an exception SizeError is raised. |
ForceFixedSize | Deprecated. Same as FixedSize . |
AllowInline | Deprecated. If set, the struct's fields will be inlined into another struct the contains this struct. |
Protected | If set, denies any overrides of that structure. If a struct is trying to override a field of it, an UnoverridableFieldError is raised. |
LockedStructure | Deprecated. Same as Protected . |
Final | Marks the structure so it won't be inheritable by any other class. If a struct is trying to inherit it, an InheritanceError is raised. |