Mutable, bit addressable, integers

Abstract

This PEP proposes adding a new built‑in factory function, mutableint(), which creates a mutable integer object(PyMutableLongObject) supporting:

● Direct bit indexing (x[i]→ get bit,x[i] = b→ set bit).

● Bit slicing (x[i:j]→ get/set contiguous bit ranges).

● Arithmetic identical to int, but preserving the mutableint type when any operand is a mutableint.

The existing int type remains immutable and unchanged, ensuring full backward compatibility.

Motivation

Bit manipulation is common in:

● Binary protocol parsing

● Cryptography

● Compression algorithms

● Embedded systems

Currently, bit access requires verbose masking and shifting:

x = 13
bit = (x >> 3) & 1
x = x | (1 << 5)

Proposed:

from builtins import mutableint

x = mutableint(13)
print(x[3]) # 1
x[5] = 1 # grows integer automatically

This is shorter, clearer, and more Pythonic.

Rationale

● Keeps int immutable by default — no risk to existing code.

● Provides an explicit opt‑in for mutability.

● Preserves Python’s arithmetic semantics while extending usability for bit‑level operations.

● Elegant numerical integer scaling from 1 bit to arbitrary length and precision.

● Avoids external dependencies (bitarray, numpy) for basic bit level manipulation.

Specification

Factory Function

mutableint(value=0) → MutableInt

● Returns a new PyMutableLongObjectinstance.

● Accepts any value convertible to int.

Bit Indexing

● x[i]returns the bit at position i(0 = LSB).

● x[i] = b sets the bit at position i to 0 or 1.

● Negative indices raise IndexError.

Bit Slicing

● x[i:j]returns a new mutableint representing bits from i(inclusive) to j(exclusive).

● x[i:j] = y sets that range of bits to the value y.

● Slices beyond the current bit length pad with zeros automatically.

Bit Length

● mutableint.bit_length()returns the total number of binary digits in the value, including leading zeros up to the current width.

● This differs from int.bit_length() which ignores leading zeros.

Example:

x = mutableint(0b00101) # width = 5 bits
print(x.bit_length()) # 5

Arithmetic Semantics

● All arithmetic operations (+,-,*,//,%,**, bitwise ops, shifts) behave exactly like int.

Type preservation rule:

○ If any integer operand is a mutableint, the result is a mutableint.

○ The result’s bit width grows automatically if needed (arbitrary precision preserved).

● Example:

a = mutableint(5)
b = 10
c = a + b
print(type(c)) # <class ‘mutableint’>

Examples

from builtins import mutableint

x = mutableint(0b1011)
print(x[0]) # 1
print(x[2]) # 0

x[2] = 1
print(bin(x)) # 0b1111

x[5] = 1
print(bin(x)) # 0b1001111

print(x[0:4]) # mutableint(15)
x[0:4] = 0b0101
print(bin(x)) # 0b1000101

Arithmetic preserves type

m = mutableint(3)
n = m + 5
print(type(n)) # <class ‘mutableint’>

Backward Compatibility

● No changes to int semantics.

● No existing code breaks.

● Mutability is explicit and opt‑in.

Implementation Notes

● Introduce a new C type:PyMutableLongObject.

● Share most of PyLongObject’s implementation for storage and arithmetic.

● Add:

○ _getitem_/__setitem__for bit access.

○ Slice handling for bit ranges with zero padding.

○ .bit_length() override to include leading zeros.

○ Arithmetic methods that preserve mutableint type when applicable.

● Ensure thread safety for in‑place updates.

Rejected Alternatives

● Making int mutable globally — breaks Python’s immutability model for numbers.

● Adding bit access to int without mutability — less useful for in‑place updates.

● Using external libraries — adds dependencies for a core numeric feature.

Next Steps

● Build a CPython proof‑of‑concept for PyMutableLongObject.

● Post this PEP draft todiscuss.python.org for feedback.

Can this really be considered a “core numeric feature” if it hasn’t been part of Python for decades?

All of the intended use-cases are going to have third-party dependencies, so I think it’s fine if this functionality also required a dependency.

6 Likes
x = x | (1 << 5)

Can be turned into

x |= 0b10000

Which is easier to read. Both versions should however already be easy to use for people commonly using binary (and therefore hex too).

bit = (x >> 3) & 1

(Or masking with 0b1000, then shifting) Is also quite easy to understand. The necessity for easier bit access is, imo, not given, as the existing methods suffice and are so common among other languages like c, that it would be confusing to use other ways. Also &|^~ and >> / << are usable in most languages in an atomic fashion, instead of methods doing any work for you. They reflect the underlying operations the best (as CPUs are basically only Boolean logic gates), so imo there is no reason to add a more confusing alternatives here.

When people are using bit access for an integer, they ought to know about &|^~ and the shifts. If you are using Python for something low-level-ish and require bit access, you should be able to explain the maths behind it.

3 Likes

I don’t think this belongs in the “typing” category.

2 Likes

Besides, this page advises against formatting your proposal as a PEP.

3 Likes

Yes, I don’t think that is a problem.

If we go back to the earliest point in computing, we have Turing machines, first implemented with punched tape memory. There is no arbitrary practical limit to the length of tape, or the number of forward and back movements (nor the interactions), but they don’t have random, or indexed access, slicing etc. What is that 1936, but today Python typing, is only around ten years old, and this has been a problem from the outset. ignored in my personal view for application domain reasons.

So yes, it is an entirely valid numerical storage model, and Python features could make it better, if only the typing system recognised the full range of numeric type sizes, from bits to integers to longs and beyond. Python typing does pretty well at the big data end of the scale, although magically switching type does have some unintended consequences. Magically switching from bits to integers could also be feasible, but I figured that developers tend to work in application oriented domains, big data is one, small data is another.

What I like about Python is the ability to quickly prototype something, but then create production ready c code, almost directly, To fulfil that role well, we need mutable bit level interaction that is not limited to the restrictions arising from only having augmented assignment operators.

I think the basic example given really just gives some understanding as to the intended functioning of the proposal, it is not an application domain example.

Yes there are augmented assignment operators, but these do not have pythonic features, indexing, slicing, scaling.

I asked co-pilot to work-up an elegant example of how to do it, with today’s restricted typing, and below is the result. I don’t think it is particularly elegant, but see if you like what you need to do when limited to augmented assignment operators?

class MutableBitView:
“”“Mutable bit-level view over a bytearray.”“”
slots = (“_data”, “_len”)

def __init__(self, data: bytearray):
    if not isinstance(data, bytearray):
        raise TypeError("MutableBitView requires a bytearray")
    self._data = data
    self._len = len(data) * 8

def __len__(self):
    return self._len

def __getitem__(self, idx):
    """Return bit at position idx (0-based)."""
    if not (0 <= idx < self._len):
        raise IndexError("Bit index out of range")
    byte_index, bit_index = divmod(idx, 8)
    return (self._data[byte_index] >> (7 - bit_index)) & 1

def __setitem__(self, idx, value):
    """Set bit at position idx to 0 or 1."""
    if not (0 <= idx < self._len):
        raise IndexError("Bit index out of range")
    if value not in (0, 1, True, False):
        raise ValueError("Bit value must be 0 or 1")
    byte_index, bit_index = divmod(idx, 8)
    mask = 1 << (7 - bit_index)
    if value:
        self._data[byte_index] |= mask   # set bit
    else:
        self._data[byte_index] &= ~mask  # clear bit

def __iter__(self):
    """Iterate over bits."""
    for i in range(self._len):
        yield self[i]

def to01(self):
    """Return bits as a string of '0' and '1'."""
    return ''.join(str(bit) for bit in self)

What the guidance actually says is this :

“It’s good to use the PEP template as a guide for what information to include..”

Yes, but not as a structure to follow. PEPs are formal documents. Discussions here are conversations. Structure your posts as conversations if you want people to engage with your ideas.

If the conversation progresses to the point where you have enough support to warrant a PEP, that is when you should think about formalising your proposal.

To make things worse, LLMs love to spew out formally structured documents, so when people see PEP style posts with a bunch of headings, they will often ignore them because they look like LLM slop.

8 Likes

What “current width”? Where is that ever set? Are you saying that bit_length always returns a fixed value?

TBH I don’t see this as a core feature deserving of a builtin; at best, it’s a library/module feature. You can use augmented assignment to get and set bits on regular integers, which is enough for simple operations; and if you need more than that, it’s quite likely that enum.IntFlag is the better choice (since you can actually name the flags instead of using magic numbers). Maybe there’s a middle ground where you need more performance than an enum but tighter syntax than aug assignment, but if there is, that’s a job for your own custom module - and I would guess that you can probably fix the width permanently, making an int32 class (or whatever size you need).

9 Likes

And a follow-on to @Rosuav’s questions: does mutableint support negative integers? If so, how does bit_length behave for those, and how would you make sense of “leading zeros” for negative integers? (And if mutableint doesn’t support negative integers, what would something like mutableint(3) - mutableint(5) do?)

Note that mutableint(0b00101).bit_length() can’t work the way you want it to (printing 5), since the value the function call sees is indistinguishable from 0b0000101 or 0b101 (for example).

4 Likes

Do you and enough others really need the integer aspect/functionality? Or does the bitarray functionality suffice?

5 Likes

Mutableint.bit_length is not set (save on assignment, or augmented assignment), extending is the standard Pythonic behaviour for integers.

bitarray is external.

With representation of a set of binary bits as a string, it is possible to get close to the Python functionality, of addressability, slicing, and extensibility, but this also needs an extension.

Why does that matter? A number of people have expressed doubt that this functionality is needed widely enough to be in the standard library.

6 Likes

Neither 2’s complement, nor negative numbers are supported for mutableint in
this proposal, that is, it is an unsigned mutableint.

There is also little difficulty to add such support, if you think that has
value, although it does add some complexity to arithmetic below a bit_length
of say 8 bits. But there are some questions. Do you use the Python object
properties to add sign? Do you double the storage to have simultaneous
representation in 2’s complement form?

Mutableint.bit_length may vary, (it is not set, though it is initialised to
bitlength=1), it may be extended on assignment, or augmented assignment.
Thus, as proposed a mutableint is extensible in a similar manner to a
regular Python integer.

At a minimum bit_length=1, what do 2’s complement and negative number
representations mean? (0 is 10 i.e. bit_length=2), and 1 is minus null)

All of this is very much getting into the weeds of a concrete proposal (and that’s kinda a mixed metaphor but honestly, weeds in concrete are usually a bad sign so let’s roll with it). Take a step back. What is the purpose of this class? What is its role in a program? Is it to store a collection of boolean flags? To prepare a bitfield for some other API that expects a properly-formatted integer? Or something else?

Start with the purpose, and then answer these questions:

  1. Why is a regular integer ill-suited to this task?
  2. Why is enum.IntFlag ill-suited to this task?

The best way to make something happen is to create something simple that serves its purpose, not a weird porting of semantics from machine code into Python.

1 Like

Not for me, and I’ve done digital design as well as machine code programming, C, Verilog, Forth, VHDL, … in my time. The only issue I have with converting bit-twiddling algorithms from, say, C to Python is the need for a 2**n-1 bit-mask to simulate fixed width ints.

1 Like

cryptography

We’ve done cryptography for years without this class. How does a mutable integer fit into its application?

Doing crypto in Python is great for research, but in production, you almost certainly want a C library anyway.

2 Likes