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
| Operator | Name | Example | Result |
|---|---|---|---|
& | AND | 46 & 74 | 10 |
| | OR | 46 | 74 | 110 |
^ | XOR | 46 ^ 74 | 100 |
~ | NOT | ~46 | -47 |
<< | left shift | 46 << 2 | 184 |
>> | right shift | 46 >> 2 | 11 |
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 << nis exactlyx * 2**n. It never overflows, so1 << 100is a 31-digit number.x >> nis floor division by2**n. For negative numbers it rounds toward negative infinity, so-7 >> 1is-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 + 1is4294967296in Python. Mask with& 0xFFFFFFFFif you need wrap-around. - Confusing
^with power.2 ^ 3is1. Use2 ** 3orpow(2, 3). - Using
&and|on lists or booleans by accident. Onset,|is union and&is intersection. Onbool,True & FalseisFalse, which is fine, butand/orshort-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.