Python __neg__: Implementing Unary Negation
python **neg**: Learn how to implement the __neg__ dunder method to support unary negation in Python custom classes, with practical examples and common pitfalls.
python neg requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
In Python, the unary minus operator (-) is backed by the __neg__ dunder method. When you write -obj on an instance of a class that defines __neg__, Python calls that method and uses its return value as the result. Without __neg__, Python raises a TypeError: bad operand type for unary -: 'MyClass'. This behavior is part of Python's operator overloading protocol, and understanding it lets you design classes that integrate naturally with built-in syntax.
What Is neg and When Is It Called
The __neg__ method is invoked whenever the unary minus operator is applied to an object. This includes expressions like -x, -obj.attr, and even -some_function(). Python looks up the method on the object's type, not the instance, so defining __neg__ on the class is sufficient. The method should return the negated value of the object. It does not modify the original object unless you explicitly mutate it inside the method.
Consider a simple class representing a 2D vector:
class Vector: def __init__(self, x, y): self.x = x self.y = y def __neg__(self): return Vector(-self.x, -self.y)
Now you can write:
v = Vector(3, -4) neg_v = -v print(neg_v.x, neg_v.y) # -3 4
Python calls Vector.__neg__ and returns a new Vector instance. The original v remains unchanged because the method returns a new object.
Implementing neg in a Custom Class
The implementation of __neg__ depends on the semantics of your class. For numeric types, it should return the arithmetic negation. For collections or other structures, it might mean element-wise negation or some domain-specific operation. The key is that the method should return a value that makes sense for the unary minus operator in your domain.
Here is a more complex example with a Money class that stores an amount and a currency:
class Money: def __init__(self, amount, currency): self.amount = amount self.currency = currency def __neg__(self): return Money(-self.amount, self.currency)
This allows -money to produce a negative amount in the same currency. Without __neg__, you would have to write Money(-money.amount, money.currency) everywhere, which is verbose and error-prone.
Returning a New Object vs Mutating Self
A common mistake is to modify the object in place inside __neg__ and return self. This is almost always wrong because unary operators in Python are expected to produce a new value, not mutate the operand. For example, if you implement:
class BadVector: def __init__(self, x, y): self.x = x self.y = y def __neg__(self): self.x = -self.x self.y = -self.y return self
Then -v would change v itself, which is surprising and breaks the principle of least astonishment. The correct approach is to return a new instance unless your class is explicitly designed to be mutable and you document that - mutates in place. In practice, returning a new object is safer and more consistent with built-in types like int and float, where -x never changes x.
How neg Interacts With pos and abs
Python provides several related unary operators: __pos__ for unary plus (+), __neg__ for unary minus (-), and __abs__ for the built-in abs() function. These methods are independent, but they often appear together in numeric classes. For a complete numeric interface, you might implement all three.
The following table summarizes their behavior:
| Method | Operator / Function | Typical Return Value |
|---|---|---|
__pos__ | +x | x itself or a copy |
__neg__ | -x | Negated value of x |
__abs__ | abs(x) | Absolute value (non-negative) |
For a vector class, __pos__ might return self (or a copy), __neg__ returns a negated vector, and __abs__ returns the magnitude. Implementing these consistently makes your class behave like a built-in numeric type.
Common Mistakes and Edge Cases
One edge case is when __neg__ is defined but returns a value of an unexpected type. For instance, if you define __neg__ to return a string, Python will not enforce a type check; it will simply return that string. This can lead to subtle bugs if callers expect the result to be the same type as the original object. Always return a value that is semantically correct for the operation.
Another mistake is forgetting to handle immutable types. If your class is immutable (e.g., you use __slots__ or freeze attributes), returning a new object is the only option. If your class is mutable, you still should return a new object unless you have a strong reason to mutate in place.
Also note that __neg__ is not automatically inherited from a base class unless the base class defines it. If you subclass a class that already implements __neg__, you may need to override it to preserve the subclass type. For example, if Vector is subclassed and __neg__ returns a Vector instead of the subclass, you lose type information. Use type(self) to construct a new instance of the correct subclass:
class Vector3D(Vector): def __init__(self, x, y, z): super().__init__(x, y) self.z = z def __neg__(self): return type(self)(-self.x, -self.y, -self.z)
Performance and Maintainability Considerations
Implementing __neg__ is generally cheap because it only involves creating a new object and possibly some arithmetic. However, if your class holds large data structures (e.g., a large matrix), returning a new object may copy significant memory. In such cases, you might consider lazy evaluation or returning a view, but that adds complexity. The standard approach is to return a new object, and the performance cost is usually acceptable unless you are in a tight loop.
From a maintainability perspective, defining __neg__ centralizes the negation logic in one place. Instead of scattering -obj.x, -obj.y across your codebase, you write -obj and let the class handle it. This reduces duplication and makes the class more intuitive to use. When the meaning of negation changes (e.g., you switch from Cartesian to polar coordinates), you only update the method.
A final consideration is compatibility with Python's data model. The __neg__ method is part of the operator protocol, and Python's standard library and third-party libraries expect it to behave consistently. If you implement __neg__, also consider implementing __pos__ and __abs__ to provide a complete set of unary operations. This makes your class more robust and less surprising to other developers who use it.