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.