To give a concrete example with bitstring:
>>> from bitstring import Bits
>>> a = [3,1,2,6,4,10] # some unsigned integers to encode
>>> p = 5 # number of bits of precision to use
Now create 5-bit bitstrings from each integer and join them together:
>>> b = Bits().join(Bits(uint=x, length=p) for x in a)
>>> b
Bits('0b000110001000001001100010001010')
Which can be converted to bytes, but note that it will be padded with zero bits up to a byte boundary if needed. When writing to a file you're always going to have a whole number of bytes as that just the way that file systems work:
>>> b.tobytes()
'\x18\x82b('
To decode it again there are a number of options, but as everything is the same length the cut
method is useful:
>>> [x.uint for x in b.cut(p)]
[3, 2, 1, 6, 4, 10]
See the docs for more information. In terms of efficiency it should be pretty good for pure Python. If you really need more speed then try the bitarray module instead, which is implemented in C and should be able to handle this problem equally well.