Bit fields data store type.
[plum.bitfields] Module Reference¶
The plum.bitfields
module provides the BitFields
class which
acts as both a transform and a data store. As a data store of a collection of
bit fields (sequences of bits), instances provide access to the individual bit
field or as an integer which comprises all the bit fields. As a transform,
the class converts instances into bytes and bytes into instances. This
reference page demonstrates creating and using a BitFields
data store as
well as provides API details.
The examples shown on this page require the following setup:
>>> from plum.bigendian import uint8
>>> from plum.bitfields import BitFields, bitfield
>>> from plum.structure import Structure, member
>>> from plum.utilities import pack, unpack
Basic Usage¶
To create your own bit fields definition, subclass BitFields
. Within the class
body, use the bitfield()
function for each bit field specifying the Python type
(if something other than a Python int
), bit position, and size (in number of bits).
>>> class Sample(BitFields):
... reserved: int = bitfield(lsb=6, size=2)
... nibble: int = bitfield(lsb=2, size=4)
... integer_flag: int = bitfield(lsb=1, size=1)
... boolean_flag: bool = bitfield(typ=bool, lsb=0, size=1)
...
When the Sample
subclass is used as the format fmt
argument,
unpacking converts bytes into an instance of this class.
>>> fmt = Sample
>>> sample = unpack(fmt, b'\x3d')
>>> sample
Sample(reserved=0, nibble=15, integer_flag=0, boolean_flag=True)
Since the BitFields
subclass instances directly support the transform
protocol, no format needs to be specified when packing:
>>> pack(sample).hex()
'3d'
Integers holding the cumulative value of all bit fields may be packed directly
when the BitFields
subclass is used as the format (no subclass instantiation
required):
>>> pack(0x3d, fmt=Sample).hex()
'3d'
The bitfield()
function defined in the Sample
subclass body
provides access to the values of the individual bitfields as attributes:
>>> sample.boolean_flag
True
>>> sample.nibble
15
A meta class (the class which constructs BitFields
subclasses) creates
a custom instance initializer for each subclass. The initializer accepts
a value for each bit field as keyword arguments:
>>> sample = Sample(boolean_flag=True, integer_flag=0, nibble=15, reserved=0)
>>> sample
Sample(reserved=0, nibble=15, integer_flag=0, boolean_flag=True)
The from_int() factory also accepts an integer value that reflects the value of all bit fields:
>>> sample = Sample.from_int(0x3d)
>>> sample
Sample(reserved=0, nibble=15, integer_flag=0, boolean_flag=True)
The dump produced by using pack_and_dump()
, unpack_and_dump()
, and the
dump()
method includes breakdowns for each bit field and lists the
attribute name as the “Access”.
>>> sample.dump()
+--------+--------------+-------+----------+--------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------------+-------+----------+--------------------+
| 0 | | 61 | 3d | Sample (BitFields) |
| [6:8] | reserved | 0 | 00...... | int |
| [2:6] | nibble | 15 | ..1111.. | int |
| [1] | integer_flag | 0 | ......0. | int |
| [0] | boolean_flag | True | .......1 | bool |
+--------+--------------+-------+----------+--------------------+
BitFields
subclassses also support getting and setting individual bits by
integer indexing:
>>> sample = Sample.from_int(0)
>>>
>>> sample[0]
False
>>> sample[0] = True
>>> int(sample)
1
>>>
>>> sample[2:8]
[False, False, False, False, False, False]
>>> sample[2:8] = [False, False, False, False, False, True]
>>> int(sample)
129
Instance Properties¶
Regardless of whether the integer bit fields type was instantiated or created with one of the unpack mechanisms, its properties are the same. The instance has integer characteristics:
>>> sample = Sample.from_int(1)
>>>
>>> sample == 1 # comparisons
True
>>>
>>> sample + 1 # arithmetic operations
2
>>> sample << 1 # logical operations
2
>>> int(sample) # conversions
1
Bit fields are settable by attribute assignment:
>>> sample = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=0)
>>> int(sample)
0
>>> sample.boolean_flag = True
>>> sample
Sample(reserved=0, nibble=0, integer_flag=0, boolean_flag=True)
>>> int(sample)
1
They support comparison against other instances:
>>> # instance vs. instance
>>> sample1 = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=0)
>>> sample2 = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=0)
>>> sample1 == sample2
True
The asdict()
method supports obtaining bit field values in dictionary form:
>>> sample = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=0)
>>> sample.asdict()
{'reserved': 0, 'nibble': 0, 'integer_flag': 0, 'boolean_flag': False}
Defaulting a Bit Field¶
To specify a default value to be utilized if a bit field value is not provided
during class instantiation (or packing), use the default
argument of the
bitfield()
definition. For example:
>>> class Sample(BitFields):
... reserved: int = bitfield(lsb=6, size=2, default=3)
... nibble: int = bitfield(lsb=2, size=4)
... integer_flag: int = bitfield(lsb=1, size=1)
... boolean_flag: bool = bitfield(typ=bool, lsb=0, size=1)
...
>>> # 'reserved' bit field not specified
>>> Sample(boolean_flag=False, integer_flag=0, nibble=0)
Sample(reserved=3, nibble=0, integer_flag=0, boolean_flag=False)
To specify a default value for undefined bit fields, use the default
argument
in the class definition. When instantiating (or packing), this provides a
starting value before supplied (or defaulted) bit field values are applied.
Normally the starting value defaults to zero when left unspecified. The
following example shows the result of a default value of 0xc0
(bits 6 and 7 set):
>>> class Sample(BitFields, default=0xc0):
... nibble: int = bitfield(lsb=2, size=4)
... integer_flag: int = bitfield(lsb=1, size=1)
... boolean_flag: bool = bitfield(lsb=0, size=1)
...
>>> Sample(boolean_flag=False, integer_flag=0, nibble=0).ipack()
b'\xc0'
Ignoring a Bit Field¶
To specify a bit field to be ignored during comparisons, set the ignore
argument to
True
in the bitfield()
definition. For example:
>>> from plum.bitfields import BitFields, bitfield
>>>
>>> class Sample(BitFields):
... reserved: int = bitfield(lsb=6, size=2, ignore=True)
... nibble: int = bitfield(lsb=2, size=4)
... integer_flag: int = bitfield(lsb=1, size=1)
... boolean_flag: bool = bitfield(lsb=0, size=1)
...
>>> sample1 = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=0)
>>> sample2 = Sample(boolean_flag=False, integer_flag=0, nibble=0, reserved=3)
>>> sample1 == sample2
True
>>> # use asdict() method to compare for exact equality
>>> sample1.asdict() == sample2.asdict()
False
To specify undefined bit fields to be ignored during comparisons, provide a bit mask
(with bits set to be ignored) with the ignore
argument in the class definition.
The following example shows the result of a ignoring bits 6 and 7 by supplying the
bit mask value of 0xc0
:
>>> class Sample(BitFields, ignore=0xc0):
... nibble: int = bitfield(lsb=2, size=4)
... integer_flag: int = bitfield(lsb=1, size=1)
... boolean_flag: bool = bitfield(lsb=0, size=1)
...
>>> sample1 = Sample.from_int(0x00)
>>> sample2 = Sample.from_int(0x80)
>>> sample1 == sample2
True
>>> # use int( ) conversion to compare exactly
>>> int(sample1) == int(sample2)
False
Alternative Bit Field Types and Nesting¶
Bit field definitions support any Python type that supports integer
conversion. The most popular choices besides int
, are
booleans and enumerations as demonstrated in the following example:
>>> from enum import IntEnum
>>> from plum.bitfields import bitfield, BitFields
>>> from plum.enum import EnumX
>>>
>>> class Letter(IntEnum):
...
... """Sample IntEnum."""
...
... A = 0
... B = 1
>>>
>>> flexible_letter = EnumX(Letter, strict=False, nbytes=1)
>>>
>>> class Sample(BitFields):
...
... """Sample BitFields subclass."""
...
... tolerant: int = bitfield(typ=flexible_letter, lsb=4, size=2)
... strict: Letter = bitfield(typ=Letter, lsb=2, size=2)
... boolean: bool = bitfield(typ=bool, lsb=0, size=1)
...
>>> sample = unpack(Sample, b'\x00')
>>> sample.boolean
False
>>> sample.strict
<Letter.A: 0>
>>> sample.tolerant
<Letter.A: 0>
>>>
>>> # non-strict enum transform allows integers not in enumeration to come through
>>> sample = unpack(Sample, b'\x30')
>>> sample.tolerant
3
Bit field definitions also support nested BitFields
subclasses as long
as the nested bitfield is defined using nested=True
:
>>> class Inner(BitFields, nested=True):
... b: int = bitfield(lsb=2, size=2)
... a: int = bitfield(lsb=0, size=2)
...
>>>
>>> class Outer(BitFields):
... j: int = bitfield(lsb=6, size=2)
... i: Inner = bitfield(typ=Inner, lsb=2, size=4)
... h: int = bitfield(lsb=0, size=2)
...
>>> sample = Outer(j=3, i=Inner(a=1, b=2), h=0)
>>> sample.i
Inner(b=2, a=1)
>>> sample.i.a
1
>>> sample.dump()
+--------+--------+-------+----------+-------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------+-------+----------+-------------------+
| 0 | | 228 | e4 | Outer (BitFields) |
| [6:8] | j | 3 | 11...... | int |
| | i | | | Inner (BitFields) |
| [4:6] | b | 2 | ..10.... | int |
| [2:4] | a | 1 | ....01.. | int |
| [0:2] | h | 0 | ......00 | int |
+--------+--------+-------+----------+-------------------+
Note
Setting nested=True
includes extra logic when getting and setting
bitfields in a nested bitfields object. The extra logic has no effect
when using the bitfields type outside of a nested application other
than a small performance degradation.
Automatic Bit Field Positions¶
Bit fields automatically position themselves when lsb
is not
specified. By default, the lsb
gets computed by placing the
bitfields in order from the most significant bit locations to the
least. For example:
>>> class AutoMix(BitFields):
...
... f1: int = bitfield(size=1)
... f2: int = bitfield(size=5)
... f3: int = bitfield(size=2)
...
>>> AutoMix.from_int(0x8b).dump()
+--------+--------+-------+----------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------+-------+----------+---------------------+
| 0 | | 139 | 8b | AutoMix (BitFields) |
| [7] | f1 | 1 | 1....... | int |
| [2:7] | f2 | 2 | .00010.. | int |
| [0:2] | f3 | 3 | ......11 | int |
+--------+--------+-------+----------+---------------------+
set fieldorder="least_to_most"
to automatically position the bit fields
in order from the least significant bit locations to the most. For example:
>>> class AutoMix(BitFields, fieldorder="least_to_most"):
...
... f1: int = bitfield(size=2)
... f2: int = bitfield(size=5)
... f3: int = bitfield(size=1)
...
>>> AutoMix.from_int(0x8b).dump()
+--------+--------+-------+----------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------+-------+----------+---------------------+
| 0 | | 139 | 8b | AutoMix (BitFields) |
| [0:2] | f1 | 3 | ......11 | int |
| [2:7] | f2 | 2 | .00010.. | int |
| [7] | f3 | 1 | 1....... | int |
+--------+--------+-------+----------+---------------------+
Controlling Overall Size and Byte Order¶
By default, the number of bytes that the overall collection of bitfields occupy
gets computed automatically based on the location and size of the bit field in
the most significant position. Use the nbytes
keyword argument when
constructing the subclass to explicitly control the number of bytes.
The transform between the byte sequence and the integer used as the
basis of the bitfields follows the little endian format. To use big endian,
set byteorder="big"
. For example:
>>> class Sample(BitFields, nbytes=2, byteorder="big"):
...
... f1: int = bitfield(size=4)
... f2: int = bitfield(size=4)
...
>>> Sample.from_int(0x0012).dump()
+--------+--------+-------+-------------------+--------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------+-------+-------------------+--------------------+
| 0 | | 18 | 00 12 | Sample (BitFields) |
| [4:8] | f1 | 1 | ........ 0001.... | int |
| [0:4] | f2 | 2 | ........ ....0010 | int |
+--------+--------+-------+-------------------+--------------------+
Example Structure Usage¶
>>> class TwoNibbles(BitFields):
... nibble1: int = bitfield(size=4)
... nibble2: int = bitfield(size=4)
...
>>> class Sample(Structure):
... nibbles: TwoNibbles = member(fmt=TwoNibbles)
... byte: int = member(fmt=uint8)
...
>>> sample = Sample(nibbles=TwoNibbles(nibble1=1, nibble2=2), byte=3)
>>> pack(sample).hex()
'1203'
>>>
>>> sample = unpack(Sample, b'\x21\x03')
>>> sample.dump()
+--------+-----------+-------+----------+------------------------+
| Offset | Access | Value | Bytes | Format |
+--------+-----------+-------+----------+------------------------+
| | | | | Sample (Structure) |
| 0 | nibbles | 33 | 21 | TwoNibbles (BitFields) |
| [4:8] | nibble1 | 2 | 0010.... | int |
| [0:4] | nibble2 | 1 | ....0001 | int |
| 1 | byte | 3 | 03 | uint8 |
+--------+-----------+-------+----------+------------------------+
Note
See Bit Field Structure Member tutorial for an alternative method of placing bit fields within a structure which avoids nesting.
API Reference¶
-
class
plum.bitfields.
BitFields
¶ Bit fields data store type.
-
byteorder
= 'little'¶
-
dump
¶ Summary containing details of bytes and layout.
-
nbytes
= 4¶
-
asdict
()¶ Return bit field values in dictionary form.
Returns: bit field names/values Return type: dict
-
classmethod
pack
(value: Any) → bytes¶ Pack value as formatted bytes.
Raises: PackError
if type error, value error, etc.
-
classmethod
pack_and_dump
(value: Any) → Tuple[bytes, plum.dump.Dump]¶ Pack value as formatted bytes and produce bytes summary.
Raises: PackError
if type error, value error, etc.
-
classmethod
unpack
(buffer: bytes) → Any¶ Unpack value from formatted bytes.
Raises: UnpackError
if insufficient bytes, excess bytes, or value error
-
classmethod
unpack_and_dump
(buffer: bytes) → Tuple[Any, plum.dump.Dump]¶ Unpack value from bytes and produce packed bytes summary.
Raises: UnpackError
if insufficient bytes, excess bytes, or value error
-
-
plum.bitfields.
bitfield
(doc: str = '', *, size: int, typ: type = <class 'int'>, lsb: Optional[int] = None, default: Optional[int] = None, signed: bool = False, ignore: bool = False, readonly: bool = False, argrepr: Optional[str] = None) → Any¶ Define bit field.
Parameters: - doc – one line description
- size – size in bits
- typ – bit field type
- lsb – bit offset of least significant bit
- default – initial value when unspecified
- signed – interpret as signed integer
- ignore – do not include field in comparisons
- readonly – block setting member attribute
- argrepr – format to represent member argument in structure repr