Ebooks

Python magic methods

Python Magic Methods tutorial describes what Python magic methods are and shows how to use them. In this tutorial we cover some common magic methods.

Python magic methods

Python magic methods are special methods that add functionality to our custom classes. They are surrounded by double underscores (e.g. __add__()).

There are many magic methods in Python. Most of them are used for very specific situations. We will mention some of the more popular methods.

The __add__ method

The __add__() method is used to implement addition operation. In Python, numbers are not primitive literals but objects. The num + 4 expression is equivalent to num.__add__(4).

add_dict.py
#!/usr/bin/env python


class MyDict(dict):

    def __add__(self, other):

        self.update(other)
        return MyDict(self)


a = MyDict({'de': 'Germany'})
b = MyDict({'sk': 'Slovakia'})

print(a + b)

In the example, we have a custom dictionary that implements the addition operation with __add__().

class MyDict(dict):

def __add__(self, other):

    self.update(other)
    return MyDict(self)

The custom dictionary inherits from the built-in dict. The __add__() method adds two dictionaries with the update() method and returns the newly created dictionary.

a = MyDict({'de': 'Germany'})
b = MyDict({'sk': 'Slovakia'})

We create two simple dictionaries.

print(a + b)

We add the two dictionaries.

$ ./add_dict.py
{'de': 'Germany', 'sk': 'Slovakia'}

This is the output.

The __init__ and __str__ methods

The __init__() method is used to initialize objects. This method is used to implement the constructor of the object. The __str__() gives a human-readable output of the object.

init_str.py
#!/usr/bin/env python


class Person:

    def __init__(self, name, occupation):

        self.name = name
        self.occupation = occupation

    def __str__(self):

        return f'{self.name} is a {self.occupation}'


p = Person('John Doe', 'gardener')
print(p)

In the example, we have a Person class with two attributes: name and occupation.

def __init__(self, name, occupation):

    self.name = name
    self.occupation = occupation

In the __init__() method we set the instance variables to the values that are passed to the constructor.

def __str__(self):

    return f'{self.name} is a {self.occupation}'

The __str__() method gives a nice short output of the object.

$ ./init_str.py
John Doe is a gardener

This is the output.

The __repr__ method

The __repr__() method is called by the built-in function repr(). It is used on the Python shell when it evaluates an expression that returns an object.

The __str__() is used to give a human-readable version of the object and the __repr__() a complete representation of the object. The output of the latter is also more suited for developers.

If __str__() implementation is missing then the __repr__() method is used as fallback.

def __repr__(self):
    return '<{0}.{1} object at {2}>'.format(
      self.__module__, type(self).__name__, hex(id(self)))

The default implementation of the __repr__() method for an object looks like the above code.

repr_ex.py
#!/usr/bin/env python


class Person:

    def __init__(self, name, occupation):
        
        self.name = name
        self.occupation = occupation

    def __str__(self):

        return f'{self.name} is a {self.occupation}'

    def __repr__(self):

        return f'Person{{name: {self.name}, occupation: {self.occupation}}}'


p = Person('John Doe', 'gardener')

print(p)
print(repr(p))

The example implements both the __str__() and the __repr__() methods.

$ ./repr_ex.py
John Doe is a gardener
Person{name: John Doe, occupation: gardener}

This is the output.

The __len__ and the __getitem__ methods

The __len__() method returns the length of the container. The method is called when we use the built-in len() method on the object. The __getitem__() method defines the item access ([]) operator.

french_deck.py
#!/usr/bin/env python

import collections
from random import choice


Card = collections.namedtuple('Card', ['suit', 'rank'])


class FrenchDeck:

    ranks = [str(i) for i in range(2, 11)] + list('JQKA')
    suits = ["heart", "clubs", "spades", "diamond"]

    def __init__(self):
        self.total = [Card(suit, rank)
                           for suit in self.suits for rank in self.ranks]

    def __len__(self):
        return len(self.total)

    def __getitem__(self, index):
        return self.total[index]


deck = FrenchDeck()

print(deck[0])
print(len(deck))
print(choice(deck))

The methods are used to implement a french card deck.

Card = collections.namedtuple('Card', ['suit', 'rank'])

We use a named tuple to define a Card class. The namedtuple is a factory function for making a tuple class. Each card has a suit and a rank.

def __len__(self):
    return len(self.total)

The __len__() method returns the number of cards in the deck (52).

def __getitem__(self, index):
    return self.total[index]

The __getitem__() implements the indexing operation.

print(deck[0])

We get the first card of the deck. This calls the __getitem__().

print(len(deck))

This calls the __len__() method.

$ ./french_deck.py
Card(suit='heart', rank='2')
52
Card(suit='diamond', rank='A')

This is the output.

The __int__ and __index__ methods

The __int__() method is called to implement the built-in int() function. The __index__() method implements type conversion to an int when the object is used in a slice expression and the built-in hex(), oct(), and bin() functions.

char_ex.py
#!/usr/bin/env python


class Char:

    def __init__(self, val):
        self.val = val

    def __int__(self):
        return ord(self.val)

    def __index__(self):
        return ord(self.val)


c1 = Char('a')

print(int(c1))
print(hex(c1))
print(bin(c1))
print(oct(c1))

In the example we create a custom Char class which implements the int(), hex(), bin(), and oct() functions.

./char_ex.py
97
0x61
0b1100001
0o141

This is the output.

The __eq__, __lt__ and __gt__ methods

The __eq__() implements the == operator. The __lt__() implements the < operator and the __gt__() implements the > operator.

pouch.py
#!/usr/bin/env python

import collections

Coin = collections.namedtuple('coin', ['rank'])

# a gold coin equals to two silver and six bronze coins


class Pouch:

    def __init__(self):
        self.bag = []

    def add(self, coin):

        self.bag.append(coin)

    def __eq__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 == val2:
            return True
        else:
            return False

    def __lt__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 < val2:
            return True
        else:
            return False

    def __gt__(self, other):

        val1, val2 = self.__evaluate(other)

        if val1 > val2:
            return True
        else:
            return False

    def __str__(self):

        return str(self.bag)

    def __evaluate(self, other):

        val1 = 0
        val2 = 0

        for coin in self.bag:

            if coin.rank == 'g':
                val1 += 6

            if coin.rank == 's':
                val1 += 3

            if coin.rank == 'b':
                val1 += 1

        for coin in other.bag:

            if coin.rank == 'g':
                val2 += 6

            if coin.rank == 's':
                val2 += 3

            if coin.rank == 'b':
                val2 += 1

        return val1, val2


pouch1 = Pouch()

pouch1.add(Coin('g'))
pouch1.add(Coin('g'))
pouch1.add(Coin('s'))

pouch2 = Pouch()

pouch2.add(Coin('g'))
pouch2.add(Coin('s'))
pouch2.add(Coin('s'))
pouch2.add(Coin('b'))
pouch2.add(Coin('b'))
pouch2.add(Coin('b'))

print(pouch1)
print(pouch2)

if pouch1 == pouch2:
    print('Pouches have equal value')

elif pouch1 > pouch2:
    print('Pouch 1 is more valueable than Pouch 2')
else:
    print('Pouch 2 is more valueable than Pouch 1')

We have a pouch that can contain gold, silver, and bronze coins. A gold coin equals to two silver and six bronze coins. In the example, we implement the three comparison operators for the pouch object using the Python magic methods.

def __eq__(self, other):

    val1, val2 = self.__evaluate(other)

    if val1 == val2:
        return True
    else:
        return False

In the __eq__() method, we first evaluate the values of the two pouches. Then we compare them and return a boolean result.

def __evaluate(self, other):

    val1 = 0
    val2 = 0

    for coin in self.bag:

        if coin.rank == 'g':
            val1 += 6

        if coin.rank == 's':
            val1 += 3

        if coin.rank == 'b':
            val1 += 1

    for coin in other.bag:

        if coin.rank == 'g':
            val2 += 6

        if coin.rank == 's':
            val2 += 3

        if coin.rank == 'b':
            val2 += 1

    return val1, val2

The __evaluate() method calculates the values of the two pouches. It goes through the coins of the pouch and adds a value according to the rank of the coin.

pouch1 = Pouch()

pouch1.add(Coin('g'))
pouch1.add(Coin('g'))
pouch1.add(Coin('s'))

We create the first pouch and add three coins to it.

if pouch1 == pouch2:
    print('Pouches have equal value')

elif pouch1 > pouch2:
    print('Pouch 1 is more valueable than Pouch 2')
else:
    print('Pouch 2 is more valueable than Pouch 1')

We compare the pouches with the comparison operators.

2D vector example

In the following example, we introduce a couple of other magic methods, including __sub__(), __mul__(), and __abs__().

vector.py
#!/usr/bin/env python

import math


class Vec2D:

    def __init__(self, x, y):

        self.x = x
        self.y = y

    def __add__(self, other):
        return Vec2D(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vec2D(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        return self.x * other.x + self.y * other.y

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

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

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

    def __ne__(self, other):
        return not self.__eq__(other)  


u = Vec2D(0, 1)
v = Vec2D(2, 3)
w = Vec2D(-1, 1)

a = u + v
print(a)

print(a == w)

a = u - v
print(a)

a = u * v

print(a)
print(abs(u))
print(u == v)
print(u != v)

In the example, we have a Vec2D class. We can compare, add, subtract, and multiply vectors. We can also calculate the lenght of a vector.

$ ./vector.py
(2, 4)
False
(-2, -2)
3
1.0
False 
True

This is the output.

In this tutorial, we have worked with Python magic methods.

You might also be interested in the following related tutorials: Python strings, Python Jinja tutorial and Python tutorial, or list all Python tutorials.