mrkeyoor.com_
Sat 08 Aug 20:58 UTC
PyPIDataupdated 08 Aug 2026

bitarray

bitarray is a C extension that stores Boolean sequences as packed bits, eight values per byte, while exposing familiar Python sequence operations. It supports slicing, mutation, bitwise operators, searches, buffer import and export, selectable bit endianness, file and byte conversion, Huffman coding, sparse compression, integer and hexadecimal helpers, and immutable hashable frozenbitarray values. It is aimed at dense flags and binary algorithms where a Python list of bool objects wastes too much memory.

Verdict

bitarray is the practical default for dense, mutable bits in Python and its C implementation offers much more than compact storage. Do not install it merely to avoid a small list of booleans, and define serialization length and endianness before data reaches disk or a network.

API stability4/5The list-like core, bitwise operators, endian model, byte methods, and utility module are mature, and `serialize()` explicitly promises a representation that will not change in future releases. Version 3 did make `search()` and `decode()` iterator-returning APIs, and newer releases add descriptors and utilities, so major-version migration notes still matter for code using the broad reference surface.
Docs5/5The README doubles as a detailed reference for every constructor, method, descriptor, decoder, and utility, with explicit version notes and warnings about list memory, padding, shifts, and endian behavior. Linked documents go deeper on buffers, indexing, canonical Huffman coding, representations, and sparse compression. It is dense, but unusually precise for a low-level extension.
Maintenance5/5Version 3.10.1 was uploaded on 2026-08-07, and GitHub reports a push on 2026-08-08. The current README demonstrates Python 3.14.5, free-threading support, type hints, and 640 self-tests. Only eight open GitHub items include both issues and pull requests, strong evidence of close maintenance across a wide Python 3.7 through 3.14 support range.
Ecosystem4/5The buffer protocol connects bitarray to NumPy, memory maps, binary streams, and other native consumers, while utility functions cover common representations and compression tasks. Weekly downloads are measured in millions and wheels cover major platforms. It is a specialized primitive, so it has fewer framework integrations than NumPy and fewer domain-level operations than compressed bitmap packages.

Use it if

  • You need millions of dense Boolean flags and want one bit per value instead of Python object references
  • Your workload spends meaningful time in bitwise intersection, union, XOR, counting, shifting, or scanning set bits
  • You need a Python object that can import or export a contiguous buffer, including memory-mapped storage
  • You are implementing prefix codes, compact integer fields, Bloom-style filters, or dense membership masks
Skip it if

Setup reality

`pip install bitarray` is usually uneventful because the project publishes wheels for major platforms and supported Python versions, and version 3.10.1 has no runtime dependencies. It supports Python 3.7 and newer and includes free-threading support for GIL-disabled CPython 3.14+. The fallback is a native source build, so unusual architectures, minimal containers, and brand-new interpreters need a C compiler plus matching Python headers. Run `python -c 'import bitarray; bitarray.test()'` when validating a custom build; the self-test is a public API and the README's current run covers 640 tests. The main setup decision is data representation. Every bitarray has `big` or `little` bit endianness, defaulting to big, and that affects bytes, files, buffers, hexadecimal conversion, and integer conversion. It is separate from the machine's byte order. Persist the endian and exact bit length in your format. `tobytes()` zero-pads an incomplete last byte, while `frombytes()` restores all eight bits in each byte, so bytes alone cannot recover a non-byte-aligned length. Use `bitarray.util.serialize()` and `deserialize()` when you control both ends and want the library's stable representation to retain metadata. Version 3 changed `search()` and `decode()` to return iterators, so old code expecting lists must wrap them explicitly. Converting a large array with `tolist()` defeats the point: the reference warns that the list can require 32 or 64 times more memory. Buffer-backed instances may be read-only, and resizing is constrained while another object exports or owns the underlying buffer. Operations are fast, but bitwise operands still need compatible lengths, and shifts preserve length rather than growing the number.

Patterns

Create a dense flag array and update rangescreate-and-mutate-bits

from bitarray import bitarray

flags = bitarray(1_000_000)
flags.setall(0)
flags[100:200] = 1
flags[150] = 0

active = flags.count(1)

An integer initializer creates that many zero bits in current bitarray. Slice assignment to a Boolean avoids allocating a temporary repeated array.

Intersect and compare dense maskscombine-bit-masks

from bitarray import bitarray
from bitarray.util import count_and, count_xor

a = bitarray('10110100')
b = bitarray('11100100')

intersection = a & b
common_count = count_and(a, b)
hamming_distance = count_xor(a, b)

count_and and count_xor avoid allocating the intermediate bitarray produced by `(a & b)` or `(a ^ b)`.

Iterate indices whose bit is oneiterate-set-bits

from bitarray import bitarray

flags = bitarray('00100101')
for index in flags.search(1):
    print(index)

Since bitarray 3.0, search returns an iterator. Wrap it in list only when every index must be retained.

Convert fixed-width signed integersconvert-integers

from bitarray.util import ba2int, int2ba

bits = int2ba(-12, length=16, endian='big', signed=True)
value = ba2int(bits, signed=True)
assert value == -12

Signed conversion uses two's complement and requires an explicit length. Values that do not fit raise OverflowError.

Round-trip hexadecimal dataconvert-hexadecimal

from bitarray.util import ba2hex, hex2ba

bits = hex2ba('dead beef', endian='big')
assert ba2hex(bits) == 'deadbeef'

Hex output requires a bit length divisible by four. The selected bit endianness changes the relationship between bit indices and encoded bytes.

Serialize exact length and endiannessserialize-with-metadata

from bitarray import bitarray
from bitarray.util import deserialize, serialize

original = bitarray('10101', endian='little')
payload = serialize(original)
restored = deserialize(payload)

assert restored == original
assert restored.endian == 'little'

Prefer serialize over bare tobytes when the bit length is not byte-aligned or the receiver must recover endianness.

Read and write a byte-oriented bit fieldexchange-raw-bytes

from bitarray import bitarray

bits = bitarray(endian='big')
bits.frombytes(b'\xa5\x10')
assert len(bits) == 16
wire_bytes = bits.tobytes()

frombytes adds eight bits for every byte. tobytes zero-pads the final byte, so store the logical length separately for partial-byte data.

View an existing writable buffer as bitsimport-buffer-without-copy

from bitarray import bitarray

buffer = bytearray([0b10100000, 0b00000001])
bits = bitarray(buffer=buffer, endian='big')
bits[0] = 0

assert buffer[0] == 0b00100000

The bitarray imports the buffer rather than copying it. Mutability follows the source buffer, and resizing a buffer-backed view is not generally available.

Use an immutable bit pattern as a dictionary keyuse-hashable-bits

from bitarray import frozenbitarray

routes = {
    frozenbitarray('1010'): 'north',
    frozenbitarray('0101'): 'south',
}
assert routes[frozenbitarray('1010')] == 'north'

Regular bitarray objects are mutable and unhashable. frozenbitarray preserves bit behavior but rejects assignment.

Build and use a Huffman codeencode-huffman-data

from collections import Counter
from bitarray import bitarray, decodetree
from bitarray.util import huffman_code

text = 'banana bandana'
code = huffman_code(Counter(text))
encoded = bitarray()
encoded.encode(code, text)

tree = decodetree(code)
decoded = ''.join(encoded.decode(tree))
assert decoded == text

decode returns an iterator. Reuse a decodetree when decoding many payloads with the same code to avoid rebuilding its internal tree.

Compress a sparse bitarraycompress-sparse-bits

from bitarray import bitarray
from bitarray.util import sc_decode, sc_encode

bits = bitarray(1_000_000)
bits.setall(0)
bits[[7, 1000, 900_000]] = 1

compressed = sc_encode(bits)
restored = sc_decode(compressed)
assert restored == bits

Sparse compression helps storage and transfer, but random access requires decoding back to a bitarray. PyRoaring may fit long-lived sparse integer sets better.

Pack one-byte flags into individual bitspack-byte-flags

from bitarray import bitarray

raw_flags = b'\x00\x01\x00\xff'
bits = bitarray()
bits.pack(raw_flags)
assert bits.to01() == '0101'
assert bits.unpack(zero=b'N', one=b'Y') == b'NYNY'

pack maps each input byte to one bit, with zero becoming 0 and every nonzero byte becoming 1. It is different from frombytes.

Alternatives

PackageRegistryPick it when
bitstringPyPIYou want readable slicing and parsing for binary protocols, files, and mixed-width integer fields
bitstructPyPIYou want struct-like format strings for packing and unpacking named-width bit fields
numpyPyPIYou need multidimensional Boolean arrays, broadcasting, and numerical operations more than one-bit storage
pyroaringPyPIYou store sparse integer sets and want compressed bitmap set operations and fast iteration