Another way to do this is by using the bitstring module:

>>> from bitstring import BitArray
>>> input_str = '0xff'
>>> c = BitArray(hex=input_str)
>>> c.bin
'0b11111111'

And if you need to strip the leading 0b:

>>> c.bin[2:]
'11111111'

The bitstring module isn't a requirement, as jcollado's answer shows, but it has lots of performant methods for turning input into bits and manipulating them. You might find this handy (or not), for example:

>>> c.uint
255
>>> c.invert()
>>> c.bin[2:]
'00000000'

etc.

Answer from Alex Reynolds on Stack Overflow
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python โ€บ convert-bytes-to-bits-in-python
Convert Bytes To Bits in Python - GeeksforGeeks
July 23, 2025 - Explanation: for byte in a: val = (val << 8) | byte constructs an integer val by shifting it left by 8 bits for each byte and OR'ing the byte. After all bytes are processed, val.bit_length() returns the number of bits needed to represent val.
Discussions

Can Python work with bits?
Learn and read about bit packing and bit masking . (Wikipedia: bit field ) You can (ab)use larger data types to transport multiple boolean flags. The first flag is at Bit 0, the second one at Bit 1, and so on. This used to be a very common approach in the old days where memory was scarce and where every bit counted. It is still heavily used in Windows flags (e.g. WindowState) and even more in PLC/DCS programming and network communication. It is just so much more efficient to pack 8 booleans in a byte, 16 booleans in a WORD, or 32 booleans in a DWORD, or 64 booleans in a QWORD for transport than trying to transport individual bits. More on reddit.com
๐ŸŒ r/learnpython
32
19
January 31, 2025
python bytes to bit string - Stack Overflow
I have value of the type bytes that need to be converted to BIT STRING bytes_val = (b'\x80\x00', 14) the bytes in index zero need to be converted to bit string of length as indicated by the second More on stackoverflow.com
๐ŸŒ stackoverflow.com
How to convert bits into bytes in python? - Stack Overflow
I'm using the following code to turn characters into bits and I don't know how to convert the bits back into their characters. I tried following the steps I took to reverse the process. I know tha... More on stackoverflow.com
๐ŸŒ stackoverflow.com
python - Construct: bytes and bits conversion - Code Review Stack Exchange
I'd also change your doctests to work in both Python 2 and Python 3. But you can skimp on them here as you're only testing the joining of the strings/bytes. And so can become: def integer2bits(number, width): r""" Converts an integer into its binary representation in a b-string. Width is the amount of bits ... More on codereview.stackexchange.com
๐ŸŒ codereview.stackexchange.com
November 11, 2016
๐ŸŒ
Theunterminatedstring
theunterminatedstring.com โ€บ python-bits-and-bytes
Python Bits and Bytes - The Unterminated String
May 19, 2018 - The smallest data unit struct can handle is a byte, so these fields must be treated as larger data units and then extracted separately via bit shifting. As this data should be in network byte order, we need to specify this with an exclamation mark, !.
๐ŸŒ
Python
wiki.python.org โ€บ moin โ€บ BitManipulation.html
BitManipulation - Python Wiki
Here is some information and goals related to Python bit manipulation, binary manipulation. ... Turn "11011000111101..." into bytes, (padded left or right, 0 or 1,) and vice versa.
๐ŸŒ
Finxter
blog.finxter.com โ€บ home โ€บ learn python blog โ€บ 5 best ways to convert python bytes to binary string
5 Best Ways to Convert Python Bytes to Binary String - Be on the Right Side of Change
February 23, 2024 - The custom function bytes_to_binary_string() uses a formatted string literal to convert each byte to an 8-bit, zero-padded binary string and joins them together. A concise one-liner using the map() function can also perform this conversion, ...
๐ŸŒ
Finxter
blog.finxter.com โ€บ home โ€บ learn python blog โ€บ 5 best ways to convert python bytes to bits
5 Best Ways to Convert Python Bytes to Bits - Be on the Right Side of Change
February 23, 2024 - By converting the bytes object to a bytearray, each byte can be iterated over and converted to bits using an f-string with the appropriate format specifier. Method 1: Bitwise Operations.
Find elsewhere
๐ŸŒ
YouTube
youtube.com โ€บ watch
How To Convert Bytes To Bits In Python With Examples
To learn more, please visit the YouTube Help Center: https://www.youtube.com/help
๐ŸŒ
Caam37830
caam37830.github.io โ€บ book โ€บ 00_python โ€บ bitsbytes.html
Bits, Bytes, and Numbers โ€” Scientific Computing with Python
However, you will have to think about this for many algorithms in scientific computing. x = 5 # an integer type print(type(x)) x = 5.0 # float type print(type(x)) ... A bit is a 0/1 value, and a byte is 8 bits. Most modern computers are 64-bit architectures on which Python 3 will use 64-bits ...
๐ŸŒ
Reddit
reddit.com โ€บ r/learnpython โ€บ can python work with bits?
r/learnpython on Reddit: Can Python work with bits?
January 31, 2025 -

My problem is that whenever I want to work with bits, let's say I want to create an 8 bit flag, Python automatically converts them to Bytes. Plus it doesn't distinguish between them. If Ilen() 8 bits, I get 8. If I len() 8 bytes I get 8. If I len() a string with 8 characters I get 8. I don't really know how should i work with bits. I can do the flags with bytes, but that seems weird. I waste 7 bits. I tried to convert a number using the bin() function which worked, but when I encoded() or sent over the network it was converted into Bytes. So 8 bytes instead of 8 bits, which means I wasted 56 bits. Any ideas?

๐ŸŒ
Bogotobogo
bogotobogo.com โ€บ python โ€บ python_bits_bytes_bitstring_constBitStream.php
Python Tutorial: bits, bytes, bitstring, and ConstBitStream - 2020
peek reads from the current bit position pos in the bitstring according to the fmt string or integer and returns the result. The bit position is unchanged. ... It reads from current bit position pos in the bitstring according the the format string and returns a single result. int:n n bits as a signed integer. uint:n n bits as an unsigned integer. hex:n n bits as a hexadecimal string. bin:n n bits as a binary string. bits:n n bits as a new bitstring. bytes:n n bytes as bytes object.
Top answer
1 of 7
6

What about some combination of formatting (below with f-string but can be done otherwise), and slicing:

def bytes2binstr(b, n=None):
    s = ' '.join(f'{x:08b}' for x in b)
    return s if n is None else s[:n + n // 8 + (0 if n % 8 else -1)]

If I understood correctly (I am not sure what the B at the end is supposed to mean), it passes your tests and a couple more:

func = bytes2binstr
args = (
    (b'\x80\x00', None),
    (b'\x80\x00', 14),
    (b'\x0f\x00', 14),
    (b'\xff\xff\xff\xff\xf0\x00', 16),
    (b'\xff\xff\xff\xff\xf0\x00', 22),
    (b'\x0f\xff\xff\xff\xf0\x00', 45),
    (b'\xff\xff\xff\xff\xf0\x00', 45),
)
for arg in args:
    print(arg)
    print(repr(func(*arg)))
# (b'\x80\x00', None)
# '10000000 00000000'
# (b'\x80\x00', 14)
# '10000000 000000'
# (b'\x0f\x00', 14)
# '00001111 000000'
# (b'\xff\xff\xff\xff\xf0\x00', 16)
# '11111111 11111111'
# (b'\xff\xff\xff\xff\xf0\x00', 22)
# '11111111 11111111 111111'
# (b'\x0f\xff\xff\xff\xf0\x00', 45)
# '00001111 11111111 11111111 11111111 11110000 00000'
# (b'\xff\xff\xff\xff\xf0\x00', 45)
# '11111111 11111111 11111111 11111111 11110000 00000'

Explanation

  • we start from a bytes object
  • iterating through it gives us a single byte as a number
  • each byte is 8 bit, so decoding that will already give us the correct separation
  • each byte is formatted using the b binary specifier, with some additional formatting: 0 zero fill, 8 minimum length
  • we join (concatenate) the result of the formatting using ' ' as "separator"
  • finally the result is returned as is if a maximum number of bits n was not specified (set to None), otherwise the result is cropped to n + the number of spaces that were added in-between the 8-character groups.

In the solution above 8 is somewhat hard-coded. If you want it to be a parameter, you may want to look into (possibly a variation of) @kederrac first answer using int.from_bytes(). This could look something like:

def bytes2binstr_frombytes(b, n=None, k=8):
    s = '{x:0{m}b}'.format(m=len(b) * 8, x=int.from_bytes(b, byteorder='big'))[:n]
    return ' '.join([s[i:i + k] for i in range(0, len(s), k)])

which gives the same output as above.

Speedwise, the int.from_bytes()-based solution is also faster:

for i in range(2, 7):
    n = 10 ** i
    print(n)
    b = b''.join([random.randint(0, 2 ** 8 - 1).to_bytes(1, 'big') for _ in range(n)])
    for func in funcs:
        print(func.__name__, funcs0 == func(b, n * 7))
        %timeit func(b, n * 7)
    print()
# 100
# bytes2binstr True
# 10000 loops, best of 3: 33.9 ยตs per loop
# bytes2binstr_frombytes True
# 100000 loops, best of 3: 15.1 ยตs per loop

# 1000
# bytes2binstr True
# 1000 loops, best of 3: 332 ยตs per loop
# bytes2binstr_frombytes True
# 10000 loops, best of 3: 134 ยตs per loop

# 10000
# bytes2binstr True
# 100 loops, best of 3: 3.29 ms per loop
# bytes2binstr_frombytes True
# 1000 loops, best of 3: 1.33 ms per loop

# 100000
# bytes2binstr True
# 10 loops, best of 3: 37.7 ms per loop
# bytes2binstr_frombytes True
# 100 loops, best of 3: 16.7 ms per loop

# 1000000
# bytes2binstr True
# 1 loop, best of 3: 400 ms per loop
# bytes2binstr_frombytes True
# 10 loops, best of 3: 190 ms per loop
2 of 7
2

you can use:

def bytest_to_bit(by, n):
    bi = "{:0{l}b}".format(int.from_bytes(by, byteorder='big'), l=len(by) * 8)[:n]
    return ' '.join([bi[i:i + 8] for i in range(0, len(bi), 8)])

bytest_to_bit(b'\xff\xff\xff\xff\xf0\x00', 45)

output:

'11111111 11111111 11111111 11111111 11110000 00000'

steps:

  1. transform your bytes to an integer using int.from_bytes

  2. str.format method can take a binary format spec.


also, you can use a more compact form where each byte is formatted:

def bytest_to_bit(by, n):
    bi = ' '.join(map('{:08b}'.format, by))
    return bi[:n + len(by) - 1].rstrip()

bytest_to_bit(b'\xff\xff\xff\xff\xf0\x00', 45)
๐ŸŒ
Finxter
blog.finxter.com โ€บ home โ€บ learn python blog โ€บ 5 best ways to convert a python list of bits to bytes
5 Best Ways to Convert a Python List of Bits to Bytes - Be on the Right Side of Change
February 27, 2024 - Here, the list of bits is joined into a string and then turned into an integer. We use string formatting to ensure that we have a full byte (8 bits) if necessary, and finally, convert it to bytes with the proper byte order using the to_bytes method.
๐ŸŒ
GeeksforGeeks
geeksforgeeks.org โ€บ python-bit-functions-on-int-bit_length-to_bytes-and-from_bytes
Python bit functions on int (bit_length, to_bytes and from_bytes) - GeeksforGeeks
August 20, 2020 - While Python provides us with two inbuilt functions to read the input from the keyboard. input ( ... Converting bytes to bits in Python involves representing each byte in its binary form, where each byte is composed of 8 bits.
๐ŸŒ
Real Python
realpython.com โ€บ python-bytes
Bytes Objects: Handling Binary Data in Python โ€“ Real Python
March 5, 2025 - As mentioned earlier, an 8-bit byte consists of 256 unique bit combinations (28). It can represent either a small unsigned integer ranging from 0 to 255, or a signed integer in the range of -128 to 127. Python only understands unsigned bytes, but there are ways to emulate signed bytes should you need toโ€”more on that later.
Top answer
1 of 3
3

You can convert the string back to an integer with int() passing a base of 2 and then back to a character with chr():

temp = format(ord('a'), 'b')
print(temp)
#'1100001'

c = chr(int(temp, 2))
print(c)
# 'a'
2 of 3
3

Mark Meyer's answer is spot on, and works for any character:

>>> char = '๐Ÿ˜Ž'
>>> bits = format(ord(char), 'b')
>>> bits
'11111011000001110'
>>> char = chr(int(bits, 2))
>>> char
'๐Ÿ˜Ž'

But it only works for characters, not for grapheme clusters. Suppose you had the woman scientist emoji:

>>> char = '๐Ÿ‘ฉโ€๐Ÿ”ฌ'
>>> bits = format(ord(char), 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 3 found

This does not work because the woman scientist emoji is not a single character, but rather a grapheme cluster made up of three characters:

  • WOMAN
  • ZERO WIDTH JOINER
  • MICROSCOPE

So the string has three characters and you can not do ord on a three character string.

I think it's important to not here that turning a single character into a bit string for its code point is highly unusual and in practice this is never really done (unless you are using an encoding known as UTF-32 BE, in which case you should pad the bit string out with zeros to 32 places). IMHO, what you should be doing here is NOT using ord and chr, but rather encoding and decoding using UTF-8. The very idea of turning characters into bits or bytes should be done with a well known character encoding scheme, and UTF-8 is the most proper.

Here is how I would suggest you do the character and bit thing:

>>> char = '๐Ÿ‘ฉโ€๐Ÿ”ฌ'
>>> bytes = char.encode('utf-8')
>>> bytes
b'\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x94\xac'
>>> char = bytes.decode('utf-8')
>>> char
'๐Ÿ‘ฉโ€๐Ÿ”ฌ'

If you want bits and not bytes, then:

>>> char = '๐Ÿ‘ฉโ€๐Ÿ”ฌ'
>>> bytes = char.encode('utf-8')
>>> bits = ''.join(f'{b:08b}' for b in bytes)
>>> bits
'1111000010011111100100011010100111100010100000001000110111110000100111111001010010101100'
Top answer
1 of 1
3

Your code is quite literally WET. I'd recommend that you merge integer2bits and integer2bytes into one private function. And for you to merge bits2integer and bytes2integer. This allows for you to reduce the amount of testing needed. As you now only need to test one function rather than two. If you pass to them lists of numbers rather than bytes or strings than you can simplify the internal logic too.

Starting with integer2bits and integer2bytes to make _integer_convert. You should be able to see that the first five lines are nearly identical. The only difference is how much you shift the number by. And so if we pass a variable size to the function to define the size of the data. You can change the shift to 1 << width * size.

def _integer_convert(number, width, size):
    if width < 1:
        raise ValueError("width must be positive")
    number = int(number)
    if number < 0:
        number += 1 << width * size

After this you should notice you're using a bit mask when passing number to int2byte. This mask is equal to (1 << size) - 1. You're also going through the number in chunks, where a chunk starts at i * size. And has the size of mask. This means that we can change the function to be:

def _integer_convert(number, width, size):
    if width < 1:
        raise ValueError("width must be positive")
    number = int(number)
    if number < 0:
        number += 1 << width * size
    mask = (1 << size) - 1
    acc = [b"\x00"] * width
    i = width - 1
    while number > 0:
        acc[i] = number & mask
        number >>= size
        i -= 1
    return acc

This is good and all, but you can change it to a generator comprehension. This is almost definitely slower than the current implementation. But can be easier to read, if you're familiar with comprehensions. The range that we'll be using will start at width-1, and end at 0. And so we can use range(width-1, -1, -1). After this I'd make the chunks start, and then perform the mask on the data. This allows for smaller code, through the use of comprehensions, but is likely to be slower than the above function. And terrible compared to the above with _integer_convert(0, 20, 1).

def _integer_convert(number, width, size):
    if width < 1:
        raise ValueError("width must be positive")
    number = int(number)
    if number < 0:
        number += 1 << width * size

    mask = (1 << size) - 1
    chunks = (i * size for i in range(width-1, -1, -1))
    return (((number & mask << chunk) >> chunk) for chunk in chunks)

To further improve this function I'd add doctests to it. Your current doctests are limited and are actually erroneous. And so using tuples as input and converting the iterator to a tuple as output can allow safer and more clear tests. I'd also test all aspects of the function. Some different sizes, and at least the sizes 1 and 8. Some different numbers, and a couple of different widths. But you almost definitely have to test a negative number. You don't do this at the moment. And I actually didn't change number += 1 << width to number += 1 << width * size originally and the new tests picked that up. And so these tests are already better than your old tests. Adding all these left me with the function:

def _integer_convert(number, width, size):
    """
    >>> tuple(_integer_convert(170, 8, 1))
    (1, 0, 1, 0, 1, 0, 1, 0)
    >>> tuple(_integer_convert(170, 4, 2))
    (2, 2, 2, 2)
    >>> tuple(_integer_convert(170, 4, 3))
    (0, 2, 5, 2)
    >>> tuple(_integer_convert(170, 4, 4))
    (0, 0, 10, 10)
    >>> tuple(_integer_convert(170, 4, 5))
    (0, 0, 5, 10)
    >>> tuple(_integer_convert(170, 4, 6))
    (0, 0, 2, 42)
    >>> tuple(_integer_convert(170, 4, 7))
    (0, 0, 1, 42)
    >>> tuple(_integer_convert(170, 2, 8))
    (0, 170)
    >>> tuple(_integer_convert(170, 1, 8))
    (170,)
    >>> _integer_convert(170, 0, 8)
    Traceback (most recent call last):
        ...
    ValueError: width must be positive
    >>> tuple(_integer_convert(-42, 8, 1))
    (1, 1, 0, 1, 0, 1, 1, 0)
    >>> tuple(_integer_convert(-170, 1, 8))
    (86,)
    >>> tuple(_integer_convert(-170, 2, 8))
    (255, 86)
    >>> tuple(_integer_convert(19, 8, 1))
    (0, 0, 0, 1, 0, 0, 1, 1)
    >>> tuple(_integer_convert(19, 2, 8))
    (0, 19)
    """
    if width < 1:
        raise ValueError("width must be positive")
    number = int(number)
    if number < 0:
        number += 1 << width * size

    mask = (1 << size) - 1
    chunks = (i * size for i in range(width-1, -1, -1))
    return (((number & mask << chunk) >> chunk) for chunk in chunks)

After this we should redefine integer2bits and integer2bytes, these are going to be rather WET, but writing everything twice rather than eight times is the lesser of two evils. I'd also change your doctests to work in both Python 2 and Python 3. But you can skimp on them here as you're only testing the joining of the strings/bytes. And so can become:

def integer2bits(number, width):
    r"""
    Converts an integer into its binary representation in a b-string. Width is the amount of bits to generate. If width is larger than the actual amount of bits required to represent number in binary, sign-extension is used. If it's smaller, the representation is trimmed to width bits. Each bit is represented as either b'\x00' or b'\x01'. The most significant is first, big-endian. This is reverse to `bits2integer`.

    Examples:

        >>> str(integer2bits(19, 8).decode('utf-8'))
        '\x00\x00\x00\x01\x00\x00\x01\x01'
    """
    return b"".join(int2byte(i) for i in _integer_convert(number, width, 1))


def integer2bytes(number, width):
    r"""
    Converts a b-string into an integer. This is reverse to `bytes2integer`.

    Examples:

        >>> str(integer2bytes(19, 4).decode('utf-8'))
        '\x00\x00\x00\x13'
    """
    return b"".join(int2byte(i) for i in _integer_convert(number, width, 8))

After this we should change bits2integer and bytes2integer in the same way. First I'd like to point out that (1 << (n - 1))*2 == 1 << n. The only difference is the latter doesn't error when n is 0. And so you can simplify that line. Another difference is number = (number << 1) | onebit2integer(b), instead I'd move onebit2integer out of this function. And so I'd change the for loop to iterate through iterateints(data) and change this line to (number << size) | b & mask. This allows for any size, and the special matches can be moved out of the function. Finally if we pass iterateints(data) as the data, we can simplify the check if the number is negative. This is as we can get the first number, or default to zero, next(iter(data), 0). And check if the correct bit is set, next(iter(data), 0) & 1 << (size - 1). And finally adding the inverted function doctests from _integer_convert, and some doctests for signed, we can get:

def _byte_convert(data, signed, size):
    r"""
    >>> _byte_convert((1, 0, 1, 0, 1, 0, 1, 0), False, 1)
    170
    >>> _byte_convert((2, 2, 2, 2), False, 2)
    170
    >>> _byte_convert((0, 2, 5, 2), False, 3)
    170
    >>> _byte_convert((0, 0, 10, 10), False, 4)
    170
    >>> _byte_convert((0, 0, 5, 10), False, 5)
    170
    >>> _byte_convert((0, 0, 2, 42), False, 6)
    170
    >>> _byte_convert((0, 0, 1, 42), False, 7)
    170
    >>> _byte_convert((0, 0, 0, 170), False, 8)
    170
    >>> _byte_convert((170,), False, 8)
    170
    >>> _byte_convert((1, 1, 0, 1, 0, 1, 1, 0), True, 1)
    -42
    >>> _byte_convert((1, 1, 0, 1, 0, 1, 1, 0), False, 1)
    214
    >>> _byte_convert((0, 1, 1, 0, 1, 0, 1, 1, 0), True, 1)
    214
    >>> _byte_convert((0, 1, 1, 0, 1, 0, 1, 1, 0), False, 1)
    214
    >>> _byte_convert((86,), True, 8)
    86
    >>> _byte_convert((255, 86), True, 8)
    -170
    >>> _byte_convert((1, 0, 1, 0, 1, 0, 1, 0), True, 1)
    -86
    >>> _byte_convert((2, 2, 2, 2), True, 2)
    -86
    >>> _byte_convert((170,), True, 8)
    -86
    """
    mask = (1 << size) - 1
    number = 0
    for num in data:
        number = (number << size) | num & mask

    if signed and next(iter(data), 0) & 1 << (size - 1):
        return number - (1 << len(data) * size)
    else:
        return number

I said that we can move the onebit2integer check out of the function for bits2integer, and we can. This is simply by passing [onebit2integer(i) for i in iteratebytes(data)] rather than list(iterateints(data)). And now both functions function the same way. So the functions bits2integer and bytes2integer can become:

def bits2integer(data, signed=False):
    r"""
    Converts a b-string into an integer. Both b'0' and b'\x00' are considered zero, and both b'1' and b'\x01' are considered one. Set sign to interpret the number as a 2-s complement signed integer. This is reverse to `integer2bits`.

    Examples:

        >>> bits2integer(b"\x01\x00\x00\x01\x01")
        19
        >>> bits2integer(b"10011")
        19
    """
    return _byte_convert([onebit2integer(i) for i in iteratebytes(data)], signed, 1)

def bytes2integer(data, signed=False):
    r"""
    Converts a b-string into an integer. This is reverse to `integer2bytes`.

    Examples:

        >>> bytes2integer(b'\x00\x00\x00\x13')
        19
    """
    return _byte_convert(list(iterateints(data)), signed, 8)

I've not really read the other functions, but they look ok.

I'd like to point out that Python's default style guide, PEP8, says that we should use snake_case for function names, and yours are not. Style guides are mostly there to create consistency, which you have and so you may want to ignore this. One way to change this could be to have two functions, one that is integer2bits which calls integer_to_bits. And have integer2bits warn on usage, after a couple of years you could, somewhat safely, remove the integer2bits function. This is as I use PEP8, and your functions look strange in my code.

My code is also unlikely to be faster than yours, as I didn't aim for speed. This is as you were duplicating code, and I'd remove that before optimizing your code.

๐ŸŒ
Real Python
realpython.com โ€บ lessons โ€บ bits-bytes-oct-hex
Working in Binary: Bits, Bytes, Oct, and Hex (Video) โ€“ Real Python
03:47 The three digits on the left easily map to three groups of 4 bits on the right-hand side. 4 bits, or half a byte, is called a nibble. Each nibble becomes a hex digit.
Published: June 30, 2020
๐ŸŒ
Agr0 Hacks Stuff
agrohacksstuff.io โ€บ posts โ€บ working-with-bytecode-in-python
Working with Bytecode in Python | Agr0 Hacks Stuff
April 5, 2024 - In my experience and personal opinion, Python is the best language by far for needling around with literal bits and bytes when you need that level of control of your data. To me, itโ€™s just an easier environment to work in. If youโ€™re doing any sort of binary exploitation, you already have pwntools to help you out there.