struct: unpack_from() Silently Ignores Trailing Bytes unpack() Rejects (tested)
struct.unpack() and struct.unpack_from() look like the same operation with an optional offset bolted on. We measured what happens when the buffer has more bytes in it than the format string accounts for -- the kind of thing that happens routinely when reading from a socket, a file, or a buffer shared with other fields.
1. unpack() rejects a buffer with extra trailing bytes
import struct
data = struct.pack('<I', 42) + b'extra_trailing_junk'
try:
print(struct.unpack('<I', data))
except struct.error as e:
print("unpack error:", e)
unpack error: unpack requires a buffer of 4 bytes
Measured: unpack() requires the buffer to be exactly the size the format string needs -- 4 bytes for '<I' -- and raises struct.error immediately if there's anything left over. This is a cheap, free sanity check: if your buffer has unexpected extra bytes, you find out right away.
2. unpack_from() on the identical buffer: no error, trailing bytes vanish silently
import struct
data = struct.pack('<I', 42) + b'extra_trailing_junk'
print(struct.unpack_from('<I', data))
(42,)
Measured: same exact bytes, same format string -- unpack_from() reads the first 4 bytes, returns (42,), and says nothing about the 19 bytes of b'extra_trailing_junk' it never looked at. This is by design -- unpack_from() exists specifically to read one record out of a larger buffer -- but it means switching from unpack() to unpack_from() (e.g. to add an offset argument) also silently drops the "did I consume everything I meant to?" check.
3. unpack_from() still checks the buffer isn't too short -- only the upper bound is unchecked
import struct
short = b'ab'
try:
print(struct.unpack_from('<I', short))
except struct.error as e:
print("short buffer error:", e)
short buffer error: unpack_from requires a buffer of at least 4 bytes for unpacking 4 bytes at offset 0 (actual buffer size is 2)
Measured: unpack_from() isn't unchecked across the board -- a buffer that's too short still raises struct.error, with a message that even names the offset and required size. It's specifically the upper bound -- trailing bytes beyond what's needed -- that goes unchecked. The struct.Struct(fmt).unpack() / .unpack_from() methods on a precompiled Struct object behave identically to the module-level functions here; precompiling doesn't change which bound gets checked.
If you need both the offset and the exact-size guarantee
import struct
def unpack_exact(fmt, buf, offset=0):
size = struct.calcsize(fmt)
if len(buf) - offset != size:
raise struct.error(
f"expected exactly {size} bytes at offset {offset}, "
f"got {len(buf) - offset}"
)
return struct.unpack_from(fmt, buf, offset)
Reach for this when a buffer is supposed to contain only one record -- for example after slicing a fixed-size chunk out of a larger stream -- and you want unconsumed trailing bytes to be a loud error instead of silently discarded data.
Frequently asked questions
Does struct.unpack_from() check that the whole buffer was consumed?
No -- we measured unpack_from() silently ignoring any bytes in the buffer beyond what the format string needs, while unpack() on the identical data raises struct.error for not being an exact-size buffer.
Does unpack_from() check anything about buffer size?
Yes, but only the lower bound -- we measured it raising struct.error when the buffer is too short to satisfy the format at the given offset. It only skips the check for a buffer that's too long.
Does struct.Struct(fmt).unpack_from() behave differently from the module-level function?
No -- we measured the Struct class's unpack()/unpack_from() methods following the exact same rule as the module-level struct.unpack()/struct.unpack_from() functions.
How do I make unpack_from() strict about trailing bytes?
Compare len(buffer) - offset against struct.calcsize(fmt) yourself and raise if they don't match exactly -- unpack_from() will not do this check for you.
Written with AI assistance. Measured on 2026-10-08 with Python 3.13.5's built-in struct module: called unpack() and unpack_from() on the same buffer, which contains extra trailing bytes beyond what the format string needs. PlainHeaders is our extension; Python's struct module is built in.