Python supports the same six bitwise operators as C and JavaScript, but its integers behave differently. A Python int has no fixed width. It grows as large as needed, and negative numbers act as if they had an infinite run of 1 bits on the left. Once you understand that one difference, the rest of Python’s bit manipulation is straightforward.

The Six Operators

OperatorNameExampleResult
&AND46 & 7410
|OR46 | 74110
^XOR46 ^ 74100
~NOT~46-47
<<left shift46 << 2184
>>right shift46 >> 211

All of them work on int (and bool, which is a subclass of int). They raise TypeError on float.

a, b = 0b00101110, 0b01001010
print(f"{a & b:08b}")   # 00001010
print(f"{a | b:08b}")   # 01101110
print(f"{a ^ b:08b}")   # 01100100

Viewing and Parsing Binary and Hex

bin(46)               # '0b101110'
hex(46)               # '0x2e'
oct(46)               # '0o56'
format(46, "08b")     # '00101110'   (8 digits, zero-padded)
f"{46:#06x}"          # '0x002e'     (# adds the prefix)

int("101110", 2)      # 46
int("2E", 16)         # 46
int("0b101110", 0)    # 46  (base 0 reads the prefix)

Literals can use underscores for readability: 0b0010_1110, 0xFF_FF.

Why ~x Is Negative

Python defines ~x as -(x + 1), which matches two’s complement at any width:

~46        # -47
~0         # -1
~-1        # 0

There is no “8-bit” or “32-bit” in plain Python, so ~46 cannot be 209 on its own. To get a fixed-width answer, mask the result:

~46 & 0xFF          # 209         (8-bit)
~46 & 0xFFFF        # 65489       (16-bit)
~46 & 0xFFFFFFFF    # 4294967249  (32-bit)

The same trick works for any operation that should wrap. Think of & ((1 << n) - 1) as “keep the low n bits”. The background is in Two’s Complement Explained.

Emulating Fixed-Width Integers

When porting C code or checking hardware values, write small helpers:

def u32(x: int) -> int:
    return x & 0xFFFFFFFF

def s32(x: int) -> int:
    x &= 0xFFFFFFFF
    return x - (1 << 32) if x & 0x80000000 else x

u32(1 << 32)        # 0          (wraps like uint32_t)
s32(0xFFFFFFFF)     # -1
s32(2**31)          # -2147483648
u32(-1)             # 4294967295

Apply u32() after every operation that could overflow, especially left shifts and additions. For a 32-bit left rotate:

def rotl32(x: int, n: int) -> int:
    n &= 31
    return u32((x << n) | (x >> (32 - n)))

Shifts in Python

  • x << n is exactly x * 2**n. It never overflows, so 1 << 100 is a 31-digit number.
  • x >> n is floor division by 2**n. For negative numbers it rounds toward negative infinity, so -7 >> 1 is -4, not -3. This is an arithmetic shift.
  • A negative shift count raises ValueError.

Python has no >>> operator. For a logical (zero-fill) right shift of a 32-bit value, mask first: (x & 0xFFFFFFFF) >> n. See Logical vs Arithmetic Right Shift for why the two differ.

Useful int Methods

(46).bit_length()     # 6   bits needed to represent 46
(46).bit_count()      # 4   number of 1 bits (Python 3.10+)
(1024).bit_length()   # 11

(46).to_bytes(2, "big")          # b'\x00.'
int.from_bytes(b"\x00\x2e", "big")  # 46
(-20).to_bytes(1, "big", signed=True)  # b'\xec'

bit_length() is handy for checking powers of two (x.bit_length() - 1 is log2(x) for exact powers) and for sizing masks.

Flags With enum.IntFlag

For named flags, the standard library has a better tool than raw integers:

from enum import IntFlag

class Perm(IntFlag):
    READ = 4
    WRITE = 2
    EXEC = 1

p = Perm.READ | Perm.WRITE
print(p)                 # Perm.READ|WRITE
print(Perm.WRITE in p)   # True
print(int(p))            # 6

IntFlag values still work with &, |, ^, and ~, but they print readable names, which makes debugging much easier. Compare with the raw-integer approach in the Bit Masking Guide.

Operator Precedence

From highest to lowest: ~, then << >>, then &, then ^, then |, then comparisons. Unlike C, Python puts comparisons below the bitwise operators, so x & 1 == 1 correctly means (x & 1) == 1. Arithmetic still binds tighter than shifts: 1 << 2 + 1 is 1 << 3, which is 8.

Common Pitfalls

  • Expecting C-style overflow. 0xFFFFFFFF + 1 is 4294967296 in Python. Mask with & 0xFFFFFFFF if you need wrap-around.
  • Confusing ^ with power. 2 ^ 3 is 1. Use 2 ** 3 or pow(2, 3).
  • Using & and | on lists or booleans by accident. On set, | is union and & is intersection. On bool, True & False is False, which is fine, but and/or short-circuit while &/| don’t.
  • Using bitwise operators on NumPy arrays without the right dtype. NumPy integers do have fixed widths and will wrap, unlike plain int.

Check Your Results

When you’re unsure what a Python expression should produce at a given width, enter the same values in the Bitwise Calculator with the width set to 8, 16, 32, or 64 bits, then compare with your masked Python output.