Image by author
In Python, magic methods help you emulate the behavior of built-in functions in your Python classes. These methods have leading and trailing double underscores (__) and are therefore also called sillier methods.
These magic methods also help you implement operator overloading in Python. You’ve probably seen examples of this. How to use the multiplication operator * with two integers gives the product. While using it with a string and an integer k
gives the rope repeated k
times:
>>> 3 * 4
12
>>> 'code' * 3
'codecodecode'
In this article, we will explore the magic methods in Python by creating a simple two-dimensional vector. Vector2D
class.
We’ll start with methods you’re probably familiar with and gradually develop more useful magical methods.
Let’s start writing some magic methods!
Consider the following Vector2D
class:
Once you create a class and instantiate an object, you can add attributes like this: obj_name.attribute_name = value
.
However, instead of manually adding attributes to each instance you create (nothing interesting, of course!), you need a way to initialize these attributes when you instantiate an object.
To do this you can define the __init__
method. Let’s define the let’s define the __init__
method for our Vector2D
class:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
v = Vector2D(3, 5)
When you try to inspect or print the object you instantiated, you’ll find that you don’t get any useful information.
v = Vector2D(3, 5)
print(v)
Output >>> <__main__.Vector2D object at 0x7d2fcfaf0ac0>
That’s why you need to add a string representation, a string representation of the object. To do this, add a __repr__
method like this:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
v = Vector2D(3, 5)
print(v)
Output >>> Vector2D(x=3, y=5)
He __repr__
It must include all attributes and information necessary to create an instance of the class. He __repr__
The method is typically used for debugging purposes.
He __str__
It is also used to add a string representation of the object. In general, the __str__
The method is used to provide information to the end users of the class.
let’s add a __str__
method for our class:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector2D(x={self.x}, y={self.y})"
v = Vector2D(3, 5)
print(v)
Output >>> Vector2D(x=3, y=5)
If not implemented __str__
falls back to __repr__
. So for every class you create, you should (at a minimum) add one __repr__
method.
Next, let’s add a method to check the equality of any two objects in the Vector2D
class. Two vector objects are equal if they have identical x and y coordinates.
Now create two Vector2D
Objects with equal values for x and y compare them to determine their equality:
v1 = Vector2D(3, 5)
v2 = Vector2D(3, 5)
print(v1 == v2)
The result is false. Because, by default, the comparison checks the equality of object IDs in memory.
let’s add the __eq__
method to check equality:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
Equality checks should now work as expected:
v1 = Vector2D(3, 5)
v2 = Vector2D(3, 5)
print(v1 == v2)
Built-in Python len()
The function helps you calculate the length of the built-in iterables. Let’s say that, for a vector, the length must return the number of elements that the vector contains.
So let’s add a __len__
method for Vector2D
class:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __len__(self):
return 2
v = Vector2D(3, 5)
print(len(v))
all the objects of the Vector2D
class are of length 2:
Now let’s think about common operations that we would perform on vectors. Let’s add magic methods to add and subtract any two vectors.
If you try to directly add two vector objects, you will encounter errors. Then you should add a __add__
method:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
Now you can add any two vectors as follows:
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
result = v1 + v2
print(result)
Output >>> Vector2D(x=4, y=7)
Next, let’s add a __sub__
method of calculating the difference between any two objects of the Vector2D
class:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
result = v1 - v2
print(result)
Output >>> Vector2D(x=2, y=3)
We can also define a __mul__
Method to define multiplication between objects.
Let’s implement, let’s manage
- Scalar multiplication: the multiplication of a vector by a scalar and
- Inner product: The scalar product of two vectors.
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __mul__(self, other):
# Scalar multiplication
if isinstance(other, (int, float)):
return Vector2D(self.x * other, self.y * other)
# Dot product
elif isinstance(other, Vector2D):
return self.x * other.x + self.y * other.y
else:
raise TypeError("Unsupported operand type for *")
Now we will take a couple of examples to see the __mul__
method in action.
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
# Scalar multiplication
result1 = v1 * 2
print(result1)
# Dot product
result2 = v1 * v2
print(result2)
Output >>>
Vector2D(x=6, y=10)
13
He __getitem__
The magic method allows you to index objects and access attributes or attribute fragments using the familiar square brackets () syntax.
for an object v
of the Vector2D
class:
v(0)
: x coordinatev(1)
: coordinate y
If you try to access by index, you will encounter errors:
v = Vector2D(3, 5)
print(v(0),v(1))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 print(v(0),v(1))
TypeError: 'Vector2D' object is not subscriptable
Let’s implement the __getitem__
method:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __getitem__(self, key):
if key == 0:
return self.x
elif key == 1:
return self.y
else:
raise IndexError("Index out of range")
You can now access the elements using their indexes as shown:
v = Vector2D(3, 5)
print(v(0))
print(v(1))
With an implementation of __call__
method, you can call objects as if they were functions.
In it Vector2D
class, we can implement a __call__
scale a vector by a given factor:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __call__(self, scalar):
return Vector2D(self.x * scalar, self.y * scalar)
So if you now call 3, you will get the vector scaled by a factor of 3:
v = Vector2D(3, 5)
result = v(3)
print(result)
Output >>> Vector2D(x=9, y=15)
He __getattr__
The method is used to obtain the values of specific attributes of objects.
For this example, we can add a __getattr__
Dunder method that is called to calculate the magnitude (L2 norm) of the vector:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __getattr__(self, name):
if name == "magnitude":
return (self.x ** 2 + self.y ** 2) ** 0.5
else:
raise AttributeError(f"'Vector2D' object has no attribute '{name}'")
Let’s check if this works as expected:
v = Vector2D(3, 4)
print(v.magnitude)
That’s all for this tutorial! I hope you learned how to add magic methods to your class to emulate the behavior of built-in functions.
We have covered some of the most useful magical methods. But this is not an exhaustive list. To enhance your understanding, create a Python class of your choice and add magic methods based on the required functionality. Keep coding!
Bala Priya C. is a developer and technical writer from India. He enjoys working at the intersection of mathematics, programming, data science, and content creation. His areas of interest and expertise include DevOps, data science, and natural language processing. He likes to read, write, code and drink coffee! Currently, he is working to learn and share his knowledge with the developer community by creating tutorials, how-to guides, opinion pieces, and more.