Python Operator Overloading: Unary Operators

In Python, unary operators are special symbols or functions that work with just one operand. You’ve probably seen them used with numbers:

-5      # Unary minus
+5      # Unary plus
~5      # Bitwise NOT
abs(-5) # Absolute value

But what if you’re working with custom classes, like a Temperature or a Vector, and you want to use the same kind of expressions? Python allows you to overload unary operators using special methods, also known as magic methods. This means you can define how your class behaves when someone writes -obj, +obj, ~obj, or abs(obj).

For example, imagine this:

temp = Temperature(-10)
print(abs(temp))   # should return a Temperature of 10
print(-temp)       # should flip the sign

By overloading the right methods, these expressions become intuitive and expressive, making your objects feel like built-in types.

The Basics of Unary Operator Overloading

Python lets you redefine how unary operators behave on custom objects by using special methods—often called dunder methods (short for “double underscore”). Here are the main unary operator methods:

OperatorMethodUse Case
-obj__neg__Negation (e.g., flipping sign)
+obj__pos__Positive (e.g., enforcing positivity)
~obj__invert__Bitwise NOT or logical inversion
abs(obj)__abs__Absolute value

When Python sees an expression like -obj, it checks if obj has a __neg__() method defined. If not, it raises a TypeError. Let’s see this in action with a simple class that doesn’t yet implement any unary operators:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __str__(self):
        return f"{self.celsius}°C"

# Create a temperature object
temp = Temperature(-15)

# Try applying a unary operator
print(-temp)  # This will raise: TypeError: bad operand type for unary -: 'Temperature'

This error happens because Python doesn’t know what -temp should mean. To fix it, we must implement the __neg__() method ourselves. The next sections will show how to do that—and much more—so our Temperature class behaves naturally with all common unary operations.

Overloading the Unary Minus (-) with __neg__

The unary minus operator (-obj) is used to return the negative of an object. For custom classes, you can define what this means by implementing the __neg__ method. Python will automatically call this method whenever - is used on an instance of your class.

Let’s use a real-world example: a Temperature class. Suppose each object represents a temperature in Celsius. Using the - operator should flip the sign—turning a cold -15°C into 15°C, or a warm 30°C into -30°C.

Here’s a complete, fun code example:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __neg__(self):
        return Temperature(-self.celsius)

    def __str__(self):
        return f"{self.celsius}°C"

# Try it out
today = Temperature(25)
freezing = Temperature(-10)

print("Today:", today)
print("Opposite of today:", -today)

print("Freezing:", freezing)
print("Opposite of freezing:", -freezing)

This makes your class more natural to use. You can now flip temperatures using the - operator just like you would with regular numbers, all thanks to __neg__().

Overloading the Unary Plus (+) with __pos__

The unary plus operator (+obj) is less common in practice, but it can still be meaningful when overloading. Python calls the __pos__ method when you use + in front of an object.

This method doesn’t have to change the object—it can return a shallow copy, enforce some constraints (like positivity), or serve as a placeholder for formatting or transformations.

Let’s demonstrate with a simple Vector2D class. In this example, +vector returns a copy of the vector but could also normalize it or apply constraints in other contexts.

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __pos__(self):
        # Return a copy of the vector
        return Vector2D(+self.x, +self.y)

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# Example usage
a = Vector2D(3, -4)
b = +a  # Triggers __pos__

print("Original:", a)
print("Unary plus (copy):", b)

Here, +a creates a new Vector2D with the same values. While it might not transform the object in this case, defining __pos__ allows your class to handle +obj expressions gracefully—and it opens the door for more meaningful operations when needed.

Overloading the Bitwise NOT (~) with __invert__

In Python, the tilde (~) is the bitwise NOT operator for integers—it flips all the bits. But when it comes to custom classes, you can redefine what ~ means by implementing the __invert__ method.

This operator becomes a powerful tool for creating expressive, readable code in domain-specific classes. A fun and useful example is a Permission class that represents access control using binary flags. Overloading ~ can let you quickly invert all permissions—turning granted permissions to denied and vice versa.

Here’s an example:

class Permission:
    def __init__(self, read=False, write=False, execute=False):
        self.read = read
        self.write = write
        self.execute = execute

    def __invert__(self):
        # Flip all permissions
        return Permission(
            read=not self.read,
            write=not self.write,
            execute=not self.execute
        )

    def __str__(self):
        return (f"Permissions("
                f"read={self.read}, "
                f"write={self.write}, "
                f"execute={self.execute})")

# Example usage
user_perm = Permission(read=True, write=False, execute=True)
flipped = ~user_perm

print("User permissions:", user_perm)
print("Inverted permissions:", flipped)

With __invert__, we make it easy to toggle permission sets using ~. This can be extended to other toggle-based logic, like game states, settings, or flags—wherever quick inversion of a state is helpful.

Overloading abs() with __abs__

Python’s built-in abs() function normally returns the absolute value of a number. But when working with custom classes, you can control what abs(obj) does by defining the __abs__ method.

This is especially useful in classes where the idea of magnitude, size, or distance is meaningful. A perfect real-world example is a 2D vector, where the absolute value can represent the vector’s length (also called its magnitude or norm). This is calculated using the Pythagorean theorem.

Here’s a clean, engaging example using a Vector2D class:

import math

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __abs__(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

    def __str__(self):
        return f"Vector2D({self.x}, {self.y})"

# Example usage
v = Vector2D(3, 4)

print("Vector:", v)
print("Magnitude:", abs(v))

In this example, calling abs(v) computes the Euclidean length of the vector using the formula √(x² + y²). This kind of overloading makes your objects feel natural and intuitive to use—just like built-in types.

Combining Unary Operators in Real-World Use

Unary operators can be powerful on their own, but they really shine when used together. When your custom class implements several unary methods, you unlock expressive, natural-looking syntax—making your code cleaner and more intuitive.

Let’s explore this by combining multiple unary operators on a single class. We’ll use a simple Vector2D class and a Flag class to demonstrate how expressions like -+vector or abs(-vector) can read like plain English while doing meaningful work.

Here’s how it works in practice:

import math

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __neg__(self):
        return Vector2D(-self.x, -self.y)

    def __pos__(self):
        return Vector2D(self.x, self.y)  # could normalize or just return a copy

    def __abs__(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)

    def __str__(self):
        return f"Vector2D({self.x}, {self.y})"


class Flag:
    def __init__(self, active=True):
        self.active = active

    def __invert__(self):
        return Flag(not self.active)

    def __str__(self):
        return "Active" if self.active else "Inactive"


# Example usage
v = Vector2D(3, 4)
f = Flag()

# Combining operators
print("Original vector:", v)
print("Negated, then positive (should remain unchanged):", +(-v))
print("Magnitude of negated vector:", abs(-v))

print("Original flag:", f)
print("Toggled flag:", ~f)
print("Toggled twice (back to original):", ~(~f))

Using combinations like -+vector, abs(-vector), or ~(~flag) makes code expressive and compact. Each operator does its part, and the class handles the logic internally. It also improves readability for others who expect objects to behave like numbers or built-in types.

Conclusion

Unary operator overloading is a subtle but powerful feature in Python. By implementing special methods like __neg__, __pos__, __invert__, and __abs__, you allow your custom classes to behave more like native types. This makes your code not only more elegant but also easier to read and reason about.

Using these operators, you can add meaning and intention to your objects. A negated temperature flips its sign. A toggled flag changes state. A vector responds to abs() with its magnitude. All of this helps turn logic into readable expression—something Python encourages deeply.

As you build your own classes, consider how these operators can help. They’re easy to implement and go a long way toward making your code feel natural. Whether you’re modeling math, state, geometry, or more—let your objects speak the language of Python.