python Bitwise operators

Python Operator Overloading: Bitwise Operators

Bitwise operators are special symbols that work on individual bits of numbers. They let you combine, change, or check bits directly. In Python, these operators include & (AND), | (OR), ^ (XOR), << (left shift), and >> (right shift).

Python lets you customize how these operators work on your own classes by using special methods like __and__ and __or__. This means you can make your objects behave like numbers when you use bitwise operators.

Why is this useful? Imagine you have a Permission system where each bit represents a different right—like reading, writing, or executing a file. By overloading bitwise operators, you can easily combine or check permissions with simple, natural expressions like read_perm & write_perm or read_perm | execute_perm. This makes your code clearer and more powerful.

In this article, we’ll explore how to overload these bitwise operators using a fun, real-world example: a Permission class that uses bits to control access rights. You’ll learn how to make your own classes support &, |, ^, <<, and >> so they work just like built-in types.

The Bitwise Operators and Their Methods

Here’s a quick table showing the bitwise operators and their special methods in Python:

OperatorMethodDescription
&__and__Bitwise AND: combines bits, only 1 if both bits are 1
|__or__Bitwise OR: combines bits, 1 if either bit is 1
^__xor__Bitwise XOR: 1 if bits differ, 0 if same
<<__lshift__Left shift: moves bits to the left, filling with 0
>>__rshift__Right shift: moves bits to the right, filling with 0

Each of these methods lets your custom class define how it should behave when used with the corresponding operator. For example, __and__ defines behavior for &, so if you write obj1 & obj2, Python calls obj1.__and__(obj2) behind the scenes.

This lets you build classes that naturally handle bitwise logic, like combining permission flags or toggling bits, just like integers but with your own rules and data structures.

Overloading & with __and__: The Bitwise AND

The __and__ method controls the behavior of the & operator. It returns the common bits set in both objects.

Here’s a real-world example using a Permission class. Each permission is represented by a bit: read (0b001), write (0b010), execute (0b100). Using & checks which permissions overlap between two Permission objects.

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __and__(self, other):
        return Permission(self.flags & other.flags)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
read = Permission(0b001)
write = Permission(0b010)

read_write = read & write  # Result: Permission(0b0), no overlap
print(read_write)

read_exec = read & Permission(0b101)  # Result: Permission(0b1), read overlaps execute flag 0b100? No, only 0b001 matches
print(read_exec)

Using & lets you find the common permissions shared between two Permission objects easily and naturally.

Overloading | with __or__: The Bitwise OR

The __or__ method controls the | operator, which combines bits from two objects, setting bits that are set in either.

Here’s how to use it with the Permission class to merge permissions:

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __or__(self, other):
        return Permission(self.flags | other.flags)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
read = Permission(0b001)
write = Permission(0b010)

combined = read | write  # Permission(0b11) – read and write combined
print(combined)

The | operator merges flags so the result grants all permissions from both objects. This makes it simple to combine permission sets naturally.

Overloading ^ with __xor__: The Bitwise XOR

The __xor__ method handles the ^ operator, which returns bits set in one object or the other, but not both.

Use case: toggling permissions — keeping only the permissions that differ between two sets.

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __xor__(self, other):
        return Permission(self.flags ^ other.flags)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
read = Permission(0b001)
write = Permission(0b010)

toggle = read ^ write  # Permission(0b11) — permissions unique to either read or write
print(toggle)

XOR is great for flipping bits, meaning it switches on bits that were off and switches off bits that were on in the other operand. This can be useful for toggling features or flags easily.

Overloading << with __lshift__: The Left Shift

The __lshift__ method handles the << operator, which moves bits to the left. This is like multiplying by powers of two for integers.

Use case: shifting permission bits left to represent higher-level permissions or new flags.

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __lshift__(self, count):
        return Permission(self.flags << count)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
read = Permission(0b001)

shifted = read << 1  # Permission(0b010), moves bits one position left
print(shifted)

Left shift moves the bits to higher positions, which is handy when you want to promote permissions or create new flags in a bitfield by shifting existing ones.

Overloading >> with __rshift__: The Right Shift

The __rshift__ method handles the >> operator, which moves bits to the right. This acts like dividing by powers of two for integers.

Use case: lowering permission levels or extracting less significant bits from a flag.

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __rshift__(self, count):
        return Permission(self.flags >> count)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
write = Permission(0b010)

shifted_back = write >> 1  # Permission(0b001), moves bits one position right
print(shifted_back)

Right shift moves bits to lower positions, useful for checking or reducing permission levels or isolating specific bits from a bitfield.

Full Example: Permission Class With All Bitwise Operators

Here is a complete Permission class combining all the bitwise operator methods. This shows how you can use &, |, ^, <<, and >> naturally with your custom class:

class Permission:
    def __init__(self, flags):
        self.flags = flags

    def __and__(self, other):
        return Permission(self.flags & other.flags)

    def __or__(self, other):
        return Permission(self.flags | other.flags)

    def __xor__(self, other):
        return Permission(self.flags ^ other.flags)

    def __lshift__(self, count):
        return Permission(self.flags << count)

    def __rshift__(self, count):
        return Permission(self.flags >> count)

    def __repr__(self):
        return f"Permission({bin(self.flags)})"

# Example usage
read = Permission(0b001)
write = Permission(0b010)
execute = Permission(0b100)

print("Read & Write:", read & write)          # Permissions common to both: 0b000
print("Read | Write:", read | write)          # Combined permissions: 0b011
print("Read ^ Write:", read ^ write)          # Permissions that differ: 0b011
print("Read shifted left by 2:", read << 2)  # Shift left: 0b100
print("Execute shifted right by 2:", execute >> 2)  # Shift right: 0b001

These operators let the Permission class behave like integers at the bit level, while keeping the code clean and meaningful by wrapping bits in a readable, well-structured class. This way, your custom objects can naturally express bitwise logic just like built-in types.

Conclusion

Overloading bitwise operators lets your classes handle complex, low-level logic with clear, readable code. By defining methods like __and__, __or__, __xor__, __lshift__, and __rshift__, you enable your objects to interact naturally using bitwise operations.

This power is especially useful in real-world domains such as managing permissions, toggling feature flags, or working with binary protocols. Experiment with these operators in your own classes to create clean, expressive, and intuitive APIs that feel just like Python’s built-in types.

Scroll to Top