If you know the list will be of length % n == 0 thenyou can use the itertools recipe called batched
def batched(iterable, n):
    "Batch data into tuples of length n. The last batch may be shorter."
    # batched('ABCDEFG', 3) --> ABC DEF G
    if n < 1:
        raise ValueError('n must be at least one')
    it = iter(iterable)
    while batch := tuple(islice(it, n)):
        yield batch
In use:
packages = [2400, 1000, 800, 2400, 1000, 1000, 2400, 1000, 1000]
for l, w, h in batched(packages, 3):
    ...
And if you want more options as far as iterables that do not have length % n == 0 length then you can use the grouper recipe:
def grouper(iterable, n, *, incomplete='fill', fillvalue=None):
    "Collect data into non-overlapping fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, fillvalue='x') --> ABC DEF Gxx
    # grouper('ABCDEFG', 3, incomplete='strict') --> ABC DEF ValueError
    # grouper('ABCDEFG', 3, incomplete='ignore') --> ABC DEF
    args = [iter(iterable)] * n
    if incomplete == 'fill':
        return zip_longest(*args, fillvalue=fillvalue)
    if incomplete == 'strict':
        return zip(*args, strict=True)
    if incomplete == 'ignore':
        return zip(*args)
    else:
        raise ValueError('Expected fill, strict, or ignore')
Finally, without using itertools, (but it's a bit hacky) from this answer:
d = iter(i)
for l, w, h in zip(*[d]*3):
    ...