Python Special Members And Magic Methods Introduction

Python has a large number of special members and “magic methods” like __doc__ which start and end with double underscores. They play a very important role and are also one of the unique syntax of python language. Some of these members are methods which should be called with parentheses, and some are properties which should be called without parentheses. Below will introduce some often used methods or properties with examples.

1. Magic Methods.

1.1 __init__().

Instantiation method. When a class instance is created, this method will be executed automatically.

class Employee:

    def __init__(self, name, age):
        print("*** Initialize Employee object.***")
        self.__name = name
        self.__age = age

if __name__ == '__main__':
    emp = Employee("tom", 88)
---------------------------------
*** Initialize Employee object.***

1.2 __del__().

Destruct method, which is triggered automatically when an object is released in memory. This method generally does not need to be customized, because Python has its own memory allocation and release mechanism, unless you need to specify some actions when releasing. Destructor calls are automatically triggered by the interpreter during garbage collection.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
    def __del__(self):
        print("*** Delete the employee object. ***")    
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    del emp
---------------------------------
*** Initialize Employee object.***
*** Delete the employee object. ***

1.3 __call__().

If you write this method for a class, the method is called when you add parentheses after the instance of the class.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
    def __del__(self):
        print("*** Delete the employee object. ***")    
        
    def __call__(self, *args, **kwargs):
        print("***__call__() method is invoked.***")
        
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    # Below code will invoke Employee class's __call__() method.
    emp()
---------------------------------------
*** Initialize Employee object.***
***__call__() method is invoked.***

1.4 __str__().

If the __str__() method is defined in a class, the return value of the method will be output by default when the object is printed. This is also a very important method, which needs to be defined by the user.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
    def __str__(self):
        return "This is the Employee class's __str__ method. "    
        
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    print(emp)

Above python code will output below message.

*** Initialize Employee object.***
This is the Employee class's __str__ method.

If do not define the __str__() method for Employee class, then print(emp) will output below text.

<__main__.Employee object at 0x100d6c128>

1.5 __getitem__()、__setitem__()、__delitem__().

Python designs three special functions: __getitem__(), __setitem__() and __delitem__() to perform actions related to brackets ( [] ). They perform value retrieve, value assignment and value delete action respectively. So if you define above three methods in your python class, then you can add brackets ( [] ) to the class instance to implement related operations like following.

# Invoke __getitem__() method.
a = obj['key']

# Invoke __setitem__() method.
obj['key'] = b

# Invoke __delitem__() method.
del obj['key']

If a class defines these three magic methods at the same time, the instance of this class behaves like a dictionary, as shown in the following example.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
            
    def __getitem__(self, name):
        print("***__getitem__() method is invoked.***")
        
    def __setitem__(self, name, age):
        print("***__setitem__() method is invoked.***")
        
    def __delitem__(self, name):
        print("***__delitem__() method is invoked.***")        
        
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    emp1 = emp['tom']
    
    emp['tom'] = 99
    
    del emp['tom']
------------------------------------
*** Initialize Employee object.***
***__getitem__() method is invoked.***
***__setitem__() method is invoked.***
***__delitem__() method is invoked.***

1.6 __iter__().

This is the iterator method. The for loop of list, dictionary and tuple can be implemented because all of them define the __iter__() method in their python source code. If you want the instance of a custom class to be iterated, you need to define the __iter__() method in the class and make the return value of the method an iteratable object. When the for loop is used in the code to traverse the object, the __iter__() method of the class will be called.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
               
    def __iter__(self):
        
        print("***__iter__() method is invoked.***")

        yield 1
        
        yield 2
        
        yield 3
        
        yield 4
        
        yield 5
        
        yield 6    
            
        
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    for i in emp:
        
        print(i)

--------------------------------
*** Initialize Employee object.***
***__iter__() method is invoked.***
1
2
3
4
5
6

1.7 __len__().

In Python, if you call the built-in len() function to get the length of an object, in the background, you actually call the __len__() method of the object, so the following code is equivalent. Python’s list, dict, str and other built-in data types all implement this method, but your custom class needs to be well designed to implement __len__() method.

str = "python"

print(str)
    
print(len(str))

print(str.__len__())
-------------------------
python
6
6

1.8 __repr__().

This method is similar to that of __str__(). The difference is that __str__() returns the string seen by the user, while __repr__() returns the string seen by the program developer, that is to say, __repr__() is for debugging. Usually the two method contains same code.

1.9 __add__().

If your python class implement __add__() method, then you can add two instance of that class.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
    def __str__(self):
        return 'Employee (%s, %d)' % (self.__name, self.__age)
        
    def __add__(self,other):
        print("***__add__() method is invoked.***")
        return Employee(self.__name + ',' + other.__name, self.__age + other.__age)      
            
        
if __name__ == '__main__': 
    
    emp = Employee("tom", 88)
    
    emp1 = Employee("jerry", 100)
    
    emp2 = emp + emp1
    
    print(emp2)
---------------------------------------
*** Initialize Employee object.***
*** Initialize Employee object.***
***__add__() method is invoked.***
*** Initialize Employee object.***
Employee (tom,jerry, 188)

2. Magic Properties.

2.1 __doc__.

Save python class or property documentation data, below is an example.

print(dict.__doc__)
----------------------------
dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
    (key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
    d = {}
    for k, v in iterable:
        d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)

2.2 __module__, __class__.

  1. __module__ : Indicate which module the object of the current operation belongs to.
  2. __class__: Indicate which class the object of the current operation belongs to.
print("module = ", dict.__module__)
    
print("class = ", dict.__class__)
-------------------------------------
module =  builtins
class =  <class 'type'>

2.3 __dict__.

List all members of a class or object. A very important and useful attribute.

class Employee: 
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
if __name__ == '__main__': 
        
    emp = Employee("tom", 88)
    
    print(emp.__dict__)
-------------------------------------------
*** Initialize Employee object.***
{'_Employee__name': 'tom', '_Employee__age': 88}

2.4 __author__.

Return the python class author information.

class Employee: 
    
    __author__ = "Jerry"
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
if __name__ == '__main__': 
    
    print(Employee.__author__)
------------------------------------------------
Jerry

2.5 __slots__.

As a dynamic language, python can add any number or any type of variables or methods to classes or objects after class definition and instantiation, this is the feature of dynamic language. For example:

# Define class Employee.
class Employee: 
    
    def __init__(self, name, age): 
        self.__name = name 
        self.__age = age 
        
    def get_name(self):
        return self.__name
    
    def get_age(self):
        return self.__age    
        
# This is a global function. It do not belong to Employee class now.
def print_employee(self):
    
    print("Employee name = ", self.get_name(),", age = ", self.get_age(),", salary = ", self.salary)
                
        
if __name__ == '__main__': 
    
    # Create a new method for class Employee, and assign the global function to this method.
    Employee.print_self = print_employee
    
    # Create a new class variable.
    Employee.salary = 10000

    # Create an instance of Employee.        
    emp = Employee("tom", 88)
    
    # Invoke above Eployee's dynamically added method.
    emp.print_self()

Below is above source code execution result.

Employee name =  tom , age =  88 , salary =  10000

But how to restrict the variables or methods that an instance can add dynamically? You can use __slots__ to restrict the variables or methods that an instance can dynamically added. In below example we only allow Employee instance can add __name, __age, salary attributes and print_self() method. It should be noted that the properties defined by __slots__ only work for the instance of the current class, but not for the subclass that inherits it.

class Employee:
    
    # Ristrict Employee class can only has __name, __age and salary properties and only can has print_self method. 
    __slots__ = ("__name","__age","salary", "print_self")
    
    def __init__(self, name, age): 
        print("*** Initialize Employee object.***") 
        self.__name = name 
        self.__age = age 
        
    def get_name(self):
        return self.__name
    
    def get_age(self):
        return self.__age    
        
    
def print_employee(self):
    
    print("Employee name = ", self.get_name(),", age = ", self.get_age(),", salary = ", self.salary)
                
        
if __name__ == '__main__': 
    
    Employee.print_self = print_employee
            
    emp = Employee("tom", 88)
    
    emp.salary = 10000
    
    emp.print_self()
    
    # Because Employee's __slots__ do not contain title, so below code will throw error AttributeError: 'Employee' object has no attribute 'title'.
    emp.title = 'Manager'