BOBOBK

Detailed Explanation of Python Magic Methods

MISCELLANEOUS

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/

Related

Recursively download files python

TECHNOLOGY
Recursively download files python

I want to back up my website recently, but the size of the file downloaded by PHP is limited, and I am too lazy to install FTP to download it. So I thought of temporarily setting up a secondary domain name site, and then using python (python3)'s requests library to directly download all the files and folders in the root directory of the website to achieve the purpose of backup.