Introduction

Python magic methods are a particular type of Python function whose specific purpose is to overload existing/built-in Python operators and/or methods.

Magic methods are defined using the __ syntax - that is the method name surrounded on each side by a double underscore. Such a syntax avoids the possibility that a programmer accidentally defines a method with the same name.

In the context of classes and object-oriented programning you can use (that is define) these magic methods such that they are called when the instances of the class are used in certain situations.

  • For example the __eq__ method can be used to define under what situations its instances should be considered equivalent. And thus, if __eq__ is defined it will be invoked if the class meets an equality test using the == operator.

  • For example, when an instance of a class is instantiated the __init__ method is invoked.

  • The point is therefore that you are not actually calling these magic methods youself, directly. Instead, it is the Python interpreter that is calling them - because by default it knows the situations/conditions under which they should be run.

Therefore, magic methods provide Python with a consistent data model across both built-in and custom classes, and thus allowing vastly different data types to interact with each other in predictable ways.

Syntax

Magic method syntax follows a consistent pattern - with the name of the method being surrounded on both sides by two underscores.

Again, this convention exists to provide the programmer a certain amount of freedom when it comes to naming their own methods and variables … assuming you stay away from naming your own objects with double underscores(!).

Each magic method serves a specific purpose:

  • For example, the __init__ method is run whenever a new instance of a class is created.
class MyClass(object):
    def __init__(self):
        print("The __init__ method is running")

>>> my_class = MyClass()
The __init__ method is running

And again, the point to note here is that its not you who is actually calling these magic methods. Instead, it is the Python interpreter that is calling them - because by default it knows the situations/conditions under which they should be run. Although the naming/spelling of the magic methods, and their signature, varies from one to the next, generally each magic method works this way.

Available Magic Methods

Creation, Initialization, and Destruction

__new__

The purpose of __new__ is to actually create and return the instance of a class. It is the first magic method to be called when an object of a class is instantiated. It is then followed by __init__.

Details:

  • The first argument to __new__ is the class of which an instance is being created (and by convention this is called cls).

  • Then, any remaining arguments to __new__ should mirror the arguments to __init__ … which is to say that all arguments that are passed to the class instantiation call are first sent to __new__ (because it is called first) before then being sent to __init__ (for initialization and customization of the instance).

  • Usually however, you don’t actually need to define, nor explicitly call, __new__. Python’s built-in implementation is adequate.

  • If a class does define __new__ its usually because the class to reference the implementation of some superclass first. And then whatever custom functionality the current class provides is then implemented.

__init__

This magic method is run just after when a class instance is created … the __init__ method of an objet runs immediately after the instance is created. It requires one positional argument (self), after which it can then take any number of positional arguments (both required and optional) and keyword arguments. This flexibility in the method signature is required because all arguments that are passed to the class instantiation call are also sent to __init__.

The purpose of __init__ is to provide initial data to the object after is has been created. That is, to customize the instance once it has been created. Therefore, in practice, __init__ does not (and should not) actually reaturn anything.

All __init__ methods return None … returning anything else will raise a TypeError. The most appropriate use of __init__ is for instantiating objects, which are created by a custom class, such that the extra variables can customize their implementation in some way.

Example … rolling a dice:

import random
class Dice(object):
    """A class representing a dice with an arbitrary number of sides"""
    def __init__(self, sides=6):
        self._sides = sides

    def roll(self):
        return random.randint(1, self._sides)
>>>standard_die = Dice()
>>>dodecahedron = Dice(sides=12)
>>>
>>>standard_die.roll()
5

__del__

The __del__ method is invoked when an object is being destroyed. The __del__ method is run regardless of how an object comes to be destroyed, whether through direct deletion invoked in ode or through memory reclamation and automatically triggered by the garbage collector. Indeed Python’s memory management is good enough that you should usually simply allow the garbage collector to delete objects that are no longer in scope and/or needed. However, if for whatever reason you need to explicitly destroy an object then the __del__ method is how this should be done.

Type conversions

__str__

  • The most commonly used type conversion magic method is __str__.
  • This method takes a single positional argument (self) and is invoked when an object is passed to the str constructor and is expected to return a string. It will also be invoked if the %s formatting expression is encountered.
class MyClass(object):
    def __str__(self):
        return "Hello World"
>>> str(MyClass())
'Hello World'

__int__, __float__, __bool__, and __complex__

  • These magic methods allow complex objects to be converted to more primitive numerical representations.
  • A representative example would be if an object defines an __int__ method, which should return an int, then it will be invoked if the object is passed to the int constructor.

Comparisons

__eq__

The __eq__ method requires the self and other arguments:

  • Thus we have the instance of the object (the self) and the other object against which it is being compared (the other).
  • It is run when Python is asked to make an equivalence check with the == operator with other being set to the object on the other side of the ==.
class MyClass(object):
    def __eq__(self, other):
        return type(self) == type(other)
>>> my_class_A = MyClass()
>>> my_class_B = MyClass()
# Ask Python to make an equivalence check, using the == operator:
>>> my_class_A == my_class_B
True
>>> MyClass() == "hello"
False

__lt__, __le__, __gt__, and __ge__

As you’d expect these map to their respective operators.

Each takes two arguments - self and other - and return True if the relative comparison should be considered to hold, and False otherwise.

Operator Overloading

  • All Python’s binary operators can be overloaded within class objects.
  • Python actually provides three magic methods for each operator, each of which takes two positional arguments, which by convention are self and other.
  • The vanilla method: behaving in the manner you’d expect and simply returning the result.
  • The reverse method: where the operand are swapped.
  • The in-place method: that are called when the operators that modify the former variable in place are used.

Conclusions

There is no reason to require that every custom class implement all magic methods … or indeed any of them.

When writing a custom class, consider what functionality you need and if that functionality maps cleanly to an existing magic method it is preferable to implement that magic method rather than provide your own custom implementation.