What are Python Magic Methods
What are magic methods? Of course, they have nothing to do with magicians. They are everywhere in object-oriented Python. They are special methods that allow you to add “magic” to your classes. These methods are automatically called during certain operations and represent the wisdom of Python’s object-oriented design. For beginners, mastering Python’s magic methods becomes especially important.
Magic methods are often named surrounded by two underscores. However, there isn’t great documentation explaining them clearly, and it’s hard to find their correct pronunciation. For example, init, do we call it “double underscore init double underscore”? It sounds awkward.
So what magic does init really have? The answer is you don’t usually call it directly. Instead, it is called automatically when instantiating a class. For example, when we use x = A()
, the class A’s __new__
and __init__
methods are called automatically.
Constructor and Initialization Magic Methods
We all know the most commonly used magic method is __init__
, the initialization method declared in a class. But __init__
is not the first magic method to be used; before initialization, __new__
is called first. This method creates an instance first, then __init__
initializes the instance. In the lifecycle of an instance, __del__
is called last to delete the instance. Let’s first take a look at these three magic methods.
__new__(cls, [...)
__new__
is the first method called in instantiating a type. After calling__new__
, it passes to__init__
. Although__new__
is less commonly used, it has unique roles. For details, see the official Python documentation.
__init__(self, [...)
__init__
is the initialization function of a class instance. It takes parameters passed to the constructor (e.g., x = ClassA(10, 'foo')
), so 10
and 'foo'
become the initialization parameters. Almost every class defines this function.
__del__(self)
Both __new__
and __init__
create a new class instance, while __del__
is the destructor called before an instance is destroyed. It’s useful when the class needs extra cleanup before being deleted, such as closing sockets or files. Note that this function cannot guarantee to always run successfully, so good programming habits like explicitly closing files or network connections are important.
Below is an example using __init__
and __del__
magic methods:
from os.path import join
class FileObject:
'''Ensure opened files are closed.'''
def __init__(self, filepath='./', filename='sample.txt'):
# Open file in r+ mode
self.file = open(join(filepath, filename), 'r+')
def __del__(self):
self.file.close()
## Close file
del self.file
Defining Operator Usage for Your Class
A big advantage of magic methods is that they let you define your own classes to behave like built-in classes, avoiding a lot of ugly and non-standard code. In other languages, you might write:
if instance.equals(other_instance):
# do something
This can work in Python too, but Python has a more elegant way: __eq__
.
def __eq__(self, other):
return self == other
This is just part of the powerful magic methods. Most magic methods let us define the meaning of operators so our functions behave like built-in ones.
Comparison Magic Methods
Python has a complete set of magic methods to define most comparison operations. We can even override the meaning of built-in functions.
Operator | Magic Method |
---|---|
< | object.lt(self, other) |
<= | object.le(self, other) |
== | object.eq(self, other) |
!= | object.ne(self, other) |
>= | object.ge(self, other) |
> | object.gt(self, other) |
Numeric Operators
Operator | Magic Method |
---|---|
+ | object.add(self, other) |
- | object.sub(self, other) |
* | object.mul(self, other) |
// | object.floordiv(self, other) |
/ | object.truediv(self, other) |
% | object.mod(self, other) |
** | object.pow(self, other[, mod]) |
« | object.lshift(self, other) |
» | object.rshift(self, other) |
& | object.and(self, other) |
^ | object.xor(self, other) |
In-place Operators
Operator | Magic Method |
---|---|
+= | object.iadd(self, other) |
-= | object.isub(self, other) |
*= | object.imul(self, other) |
/= | object.idiv(self, other) |
//= | object.ifloordiv(self, other) |
%= | object.imod(self, other) |
**= | object.ipow(self, other[, mod]) |
«= | object.ilshift(self, other) |
»= | object.irshift(self, other) |
&= | object.iand(self, other) |
^= | object.ixor(self, other) |
= |
Unary Operators
Operator | Magic Method |
---|---|
- | object.neg(self) |
+ | object.pos(self) |
abs() | object.abs(self) |
~ | object.invert(self) |
complex() | object.complex(self) |
int() | object.int(self) |
long() | object.long(self) |
float() | object.float(self) |
oct() | object.oct(self) |
hex() | object.hex(self) |
Magic Method Example
Here we define a Length class that can perform length calculations in different units.
class Length:
__metric = {"mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000,
"in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144,
"mi" : 1609.344 }
def __init__(self, value, unit = "m" ):
self.value = value
self.unit = unit
def Converse2Metres(self):
return self.value * Length.__metric[self.unit]
def __add__(self, other):
l = self.Converse2Metres() + other.Converse2Metres()
return Length(l / Length.__metric[self.unit], self.unit )
def __str__(self):
return str(self.Converse2Metres())
def __repr__(self):
return "Length(" + str(self.value) + ", '" + self.unit + "')"
if __name__ == "__main__":
x = Length(4)
print(x)
y = eval(repr(x))
z = Length(4.5, "yd") + Length(1)
print(repr(z))
print(z)
Output:
4
Length(5.593613298337708, 'yd')
5.1148
The __call__
Method
Many times when writing Python you encounter the error "TypeError: 'xxx' object is not callable"
, meaning the class instance is not callable. Defining the __call__
method makes your custom class callable. Here is an example:
class Polynomial:
def __init__(self, *coefficients):
self.coefficients = coefficients[::-1]
def __call__(self, x):
res = 0
for index, coeff in enumerate(self.coefficients):
res += coeff * x** index
return res
# a constant function
p1 = Polynomial(42)
# a straight Line
p2 = Polynomial(0.75, 2)
# a third degree Polynomial
p3 = Polynomial(1, -0.5, 0.75, 2)
for i in range(1, 10):
print(i, p1(i), p2(i), p3(i))
Output:
2 42 3.5 9.5
3 42 4.25 26.75
4 42 5.0 61.0
5 42 5.75 118.25
6 42 6.5 204.5
7 42 7.25 325.75
8 42 8.0 488.0
9 42 8.75 697.25
References:
https://www.python-course.eu/python3_magic_methods.php
https://rszalski.github.io/magicmethods/