Bitwise Operators in Python

Six operators that work on the individual bits of an integer. Rarely needed in everyday code, routinely needed for flags, masks, permissions and interview questions.

What bitwise means

Every integer is stored as a pattern of bits. Bitwise operators ignore the numeric value and work on those bits one position at a time.

print(bin(12))     # 0b1100
print(bin(10))     # 0b1010
OperatorNameBit rule12 and 10
&AND1 only if both bits are 18
|OR1 if either bit is 114
^XOR1 if the bits differ6
~NOTFlips every bit~12 is -13
<<Left shiftMoves bits left, filling with 012 << 1 is 24
>>Right shiftMoves bits right, discarding12 >> 1 is 6

Working through the arithmetic

   12 = 1 1 0 0
   10 = 1 0 1 0
        -------
    &   1 0 0 0  = 8
    |   1 1 1 0  = 14
    ^   0 1 1 0  = 6
print(12 & 10)     # 8
print(12 | 10)     # 14
print(12 ^ 10)     # 6
print(~12)         # -13
print(12 << 1)     # 24
print(12 >> 1)     # 6

Why ~12 is -13

Python integers are conceptually signed and of unlimited width, and negative numbers use two's complement. Flipping every bit of n always yields -(n + 1). That single identity is easier to remember than the bit pattern.

for n in [0, 1, 5, 12]:
    print(n, "->", ~n)      # 0 -> -1, 1 -> -2, 5 -> -6, 12 -> -13

Shifting is multiplying and dividing by powers of two

print(5 << 1)      # 10   same as 5 * 2
print(5 << 3)      # 40   same as 5 * 2 ** 3
print(40 >> 3)     # 5    same as 40 // 2 ** 3
print(-7 >> 1)     # -4   floors, exactly like //
Do not use shifting as a speed trick in Python. The interpreter overhead dominates, so n * 2 is not measurably slower than n << 1, and it is far clearer. Shift when you mean bits, multiply when you mean arithmetic.

The real use: flags and masks

Bitwise operators earn their place when several yes or no settings are packed into one integer. File permissions, hardware registers and network protocols all work this way.

READ    = 0b0001    # 1
WRITE   = 0b0010    # 2
EXECUTE = 0b0100    # 4
DELETE  = 0b1000    # 8

# Grant several permissions at once.
permissions = READ | WRITE
print(bin(permissions))          # 0b11

# Test a permission.
print(bool(permissions & READ))       # True
print(bool(permissions & EXECUTE))    # False

# Add one.
permissions |= EXECUTE
print(bool(permissions & EXECUTE))    # True

# Remove one.
permissions &= ~WRITE
print(bool(permissions & WRITE))      # False

# Toggle one.
permissions ^= DELETE
print(bool(permissions & DELETE))     # True

Those four lines - set with |, test with &, clear with &= ~, toggle with ^ - are the entire vocabulary of flag handling.

Classic bit tricks

# Is n even?
n = 14
print(n & 1 == 0)         # True; the last bit is the parity

# Is n a power of two?
def is_power_of_two(n):
    return n > 0 and n & (n - 1) == 0


print(is_power_of_two(16))    # True
print(is_power_of_two(18))    # False

# Count the set bits.
print(bin(29).count("1"))     # 4
print((29).bit_count())       # 4, Python 3.10 and later

# Swap two numbers without a temporary (a curiosity, not a recommendation).
a, b = 5, 9
a ^= b
b ^= a
a ^= b
print(a, b)                   # 9 5

n & (n - 1) clears the lowest set bit. If that leaves zero, there was exactly one set bit, so n was a power of two. This appears in interviews constantly.

Useful integer methods

print((255).bit_length())          # 8, bits needed to represent it
print((255).to_bytes(2, "big"))    # b'\x00\xff'
print(int.from_bytes(b"\x01\x00", "big"))   # 256

Bitwise operators on sets

The same symbols are reused by set, where they mean intersection, union, difference and symmetric difference. That is covered fully in the sets note; it is mentioned here so the shared symbols are not a surprise.

a = {1, 2, 3}
b = {2, 3, 4}
print(a & b)      # {2, 3}
print(a | b)      # {1, 2, 3, 4}
print(a ^ b)      # {1, 4}

Common mistakes

  • Confusing & with and. 5 & 3 is 1; 5 and 3 is 3. They are unrelated.
  • Expecting ~5 to be -5. It is -6.
  • Using bitwise operators on floats. It is a TypeError; bits are defined for integers only.
  • Forgetting that & binds more loosely than ==, so x & 1 == 0 parses as x & (1 == 0). Bracket it: (x & 1) == 0.
  • Using shifts for arithmetic in the belief that it is faster.

Best practices

  • Name your flags as constants in capitals and define them as powers of two.
  • Bracket bitwise expressions generously; the precedence rules rarely match intuition.
  • Use bin() while learning and debugging so you can see what happened.
  • Consider enum.Flag from the standard library for real permission systems; it gives the same behaviour with readable names.

Practice

  1. Compute 25 & 19, 25 | 19 and 25 ^ 19 by writing out the bits, then check with Python.
  2. Explain why ~n == -(n + 1) holds for every integer.
  3. Define four permission flags and write functions to grant, revoke, toggle and test one.
  4. Write a function that returns the number of 1 bits in an integer without using bin or bit_count.
  5. Explain why x & 1 == 0 does not do what it looks like, and fix it.

Conclusion

Bitwise operators are a small, self contained corner of Python. You will not need them often, but when you meet packed flags, protocol headers or an interview question about powers of two, nothing else will do the job as directly.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.