ZetCode

Python __ipow__ Method

Last modified April 8, 2025

This comprehensive guide explores Python's __ipow__ method, the special method for in-place exponentiation. We'll cover basic usage, numeric operations, operator overloading, and practical examples.

Basic Definitions

The __ipow__ method implements the in-place exponentiation operation (**=). It should modify the object in-place and return the result.

Key characteristics: it modifies the object's state directly, returns self (unless immutable), and is called for **= operations. If not implemented, Python falls back to __pow__.

Basic __ipow__ Implementation

Here's a simple implementation showing how __ipow__ works with numeric types. The method modifies the object and returns itself.

basic_ipow.py
class Number:
    def __init__(self, value):
        self.value = value
    
    def __ipow__(self, other):
        self.value **= other
        return self
    
    def __repr__(self):
        return f"Number({self.value})"

num = Number(2)
num **= 3
print(num)  # Number(8)

This example shows a basic __ipow__ implementation. The method raises the stored value to the given power and returns the modified object.

The **= operator calls __ipow__ when available, providing more efficient in-place operations than creating new objects.

__ipow__ with Custom Matrix Class

For matrix operations, __ipow__ can provide efficient in-place matrix exponentiation, avoiding temporary object creation.

matrix_ipow.py
class Matrix:
    def __init__(self, data):
        self.data = data
    
    def __ipow__(self, power):
        if not isinstance(power, int) or power < 0:
            raise ValueError("Power must be non-negative integer")
        
        result = [[1 if i == j else 0 for j in range(len(self.data))]
                 for i in range(len(self.data))]
        
        for _ in range(power):
            result = [[sum(a*b for a,b in zip(row, col))
                      for col in zip(*self.data)] for row in result]
        
        self.data = result
        return self
    
    def __repr__(self):
        return '\n'.join(' '.join(map(str, row)) for row in self.data)

m = Matrix([[1, 1], [1, 0]])
m **= 3
print(m)

This matrix class implements in-place matrix exponentiation. The __ipow__ method performs matrix multiplication in a loop, updating the internal data.

The implementation checks for valid power values and uses list comprehensions for efficient matrix operations. This avoids creating intermediate matrix objects.

Immutable Objects and __ipow__

For immutable objects, __ipow__ should return a new object rather than modifying the existing one, similar to how tuples handle in-place operations.

immutable_ipow.py
class ImmutableNumber:
    def __init__(self, value):
        self._value = value
    
    def __ipow__(self, other):
        return ImmutableNumber(self._value ** other)
    
    def __pow__(self, other):
        return ImmutableNumber(self._value ** other)
    
    @property
    def value(self):
        return self._value
    
    def __repr__(self):
        return f"ImmutableNumber({self.value})"

num = ImmutableNumber(3)
num **= 4
print(num)  # ImmutableNumber(81)

This immutable number class returns a new instance from __ipow__ instead of modifying itself. The **= operator rebinds the variable.

Note that both __ipow__ and __pow__ are implemented for consistency. The immutable nature means in-place operation isn't truly in-place.

__ipow__ with Modulo Parameter

The __ipow__ method can optionally support a modulo parameter, similar to the built-in pow() function with three arguments.

modulo_ipow.py
class ModNumber:
    def __init__(self, value):
        self.value = value
    
    def __ipow__(self, args):
        if isinstance(args, tuple):
            power, mod = args
            self.value = pow(self.value, power, mod)
        else:
            self.value **= args
        return self
    
    def __repr__(self):
        return f"ModNumber({self.value})"

num = ModNumber(3)
num **= 4
print(num)  # ModNumber(81)

num = ModNumber(3)
num **= (4, 10)  # 3^4 mod 10
print(num)  # ModNumber(1)

This implementation checks if the argument is a tuple (for modulo operation) or a single value (regular exponentiation). It uses Python's built-in pow().

The modulo parameter is useful for cryptographic operations and modular arithmetic where intermediate results need to be kept within bounds.

Fallback to __pow__ Behavior

When __ipow__ isn't implemented, Python falls back to __pow__ and regular assignment. This example demonstrates the difference.

fallback_ipow.py
class PowOnly:
    def __init__(self, value):
        self.value = value
    
    def __pow__(self, other):
        return PowOnly(self.value ** other)
    
    def __repr__(self):
        return f"PowOnly({self.value})"

class IPow(PowOnly):
    def __ipow__(self, other):
        self.value **= other
        return self

a = PowOnly(2)
b = PowOnly(2)

a **= 3
print(a)  # Shows new object id
print(a is b)  # False

x = IPow(2)
y = x

x **= 3
print(x is y)  # True, same object modified

The PowOnly class creates new objects on each operation, while IPow modifies the existing object. The is checks demonstrate this difference.

Understanding this fallback behavior is important when designing classes that should support in-place operations efficiently.

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.