<jupyter_start><jupyter_text>Classes ( contd... )--- - magic methods starts with \_\_ and ends with \_\_ *Note: tripple quotes represents docstrings for class and methods*  Overloading operators<jupyter_code>class Temperature:
    """
    Holds temperature data and processes with it
    """
    
    def __init__(self, initial):
        """
        initialization
        """
        self.initial = initial
help(Temperature)<jupyter_output>Help on class Temperature in module __main__:

class Temperature(builtins.object)
 |  Holds temperature data and processes with it
 |  
 |  Methods defined here:
 |  
 |  __init__(self, initial)
 |      initialization
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)<jupyter_text>*Create a temperature instance*<jupyter_code>t1 = Temperature(20)
t1.initial<jupyter_output><empty_output><jupyter_text>*Create another temperature instance*<jupyter_code>t2 = Temperature(30)
t2.initial<jupyter_output><empty_output><jupyter_text>*Now Add two temperature instances*<jupyter_code>t1 + t2
class Temperature:
    """
    Holds temperature data and processes with it
    """
    
    def __init__(self, initial):
        """
        initialization
        """
        self.initial = initial
    
    def __add__(self, other):
        """
        adds two temperature instances
        """
        return self.initial + other.initial
t1 = Temperature(20)
t2 = Temperature(30)
t1 + t2<jupyter_output><empty_output><jupyter_text>**What actually happened above**```python When we call something like thist1 + t1 it looks fort1.__add__(t2) if it is not able to find that method it throws exception otherwise just proceed the method as defined in our case, t1 is `self` and t2 is `other` so it returns t1.initial + t2.initial``` *What happens if we try to substract two instances*<jupyter_code>t1 - t2<jupyter_output><empty_output><jupyter_text>*This is because we have not defined \_\_sub\_\_ method above, which is for minus (-)* *Not only + or -, other operators can be overloaded too such as*```pythondef __mul__(self, other):    """    multiplication operator ( * )    """    passdef __truediv__(self, other):    """    true division ( float ) operator ( / )    """    passdef __floordiv__(self, other):    """    floor division ( int ) operator ( // )    """    passdef __mod__(self, other):    """    modulus operator ( % )    """    passdef __iadd__(self, other):    """    inplace addition operator ( += )    """    passdef __isub__(self, other):    """    inplace substraction operator ( -= )    """    pass```checkout http://www.diveintopython3.net/special-method-names.html for complete list<jupyter_code>dir(Temperature)
Temperature.__doc__
Temperature.__dict__
t1
class Temperature:
    """
    Holds temperature data and processes with it
    """
    
    def __init__(self, initial):
        """
        initialization
        """
        self.initial = initial
    
    def __add__(self, other):
        """
        adds two temperature instances
        """
        return self.initial + other.initial
    
    def __repr__(self):
        return 'Initial Temperature is {}'.format(self.initial)
t3 = Temperature(40)
t3<jupyter_output><empty_output><jupyter_text>Inheritence >In object-oriented programming, inheritance is when an object or class is based on another object (prototypal inheritance) or class (class-based inheritance), using the same implementation (inheriting from an object or class) specifying implementation to maintain the same behavior    - Wikipedia *In other words, Inheritence is a way of sharing common features or attributes between different classes.*<jupyter_code>class Rectangle:
    """
    This is the parent class
    """
    width = None
    height = None
    
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        """
        All class that inherites from this class will inherit 
        area method automatically
        """
        return self.width * self.height
class Square(Rectangle):
    """
    This is child class
    """
    
    def __init__(self, side):
        """
        Square is special rectangle with equal sides.
        so our height and width will be same
        """
        # we are calling __init__ of parent/super class using super()
        super().__init__(side, side)<jupyter_output><empty_output><jupyter_text>*let's create a rectangle*<jupyter_code>rec = Rectangle(3, 4)
rec.area()<jupyter_output><empty_output><jupyter_text>*let's create a square*<jupyter_code>sq = Square(4)
sq.area()<jupyter_output><empty_output><jupyter_text>*Note: Remember the area is defined in Rectangle class, but instance of Square is using it, because it is child of Rectangle and inherits all methods defined in Rectangle automatically.* **Overriding or altering parent methods**<jupyter_code>class Shape:
    name = None
    color = 'white'
    
    def shape_type(self):
        return self.name
    
    def area(self):
        return 0
    
    def shape_color(self):
        print("The color of Shape is {}".format(self.color))


class Circle(Shape):
    radius = None

    def __init__(self, radius):
        self.radius = radius
        self.name = 'Circle'
    
    def area(self):
        """
        We override area method for circle
        """
        return 2 * 3.14 * (self.radius ** 2)
    
    def shape_color(self):
        """
        We add few extra information for color
        """
        super().shape_color()
        print("The color of Circle is {}".format(self.color))


class Square(Shape):
    side = None

    def __init__(self, side):
        self.side = side
        self.name = 'Square'
    
    def area(self):
        """
        We override area method for square
        """
        return self.side * self.side
    
    def shape_color(self):
        """
        We add few extra information for color
        """
        super().shape_color()
        self.color = 'Red'
        print("The color of Square is {}".format(self.color))
circle = Circle(2)
circle.shape_type()
circle.area()
circle.shape_color()
square = Square(4)
square.shape_type()
square.shape_color()
square.area()<jupyter_output><empty_output><jupyter_text>*In above example*- we completely changed the behavior of method __area__- we didn't do anything with method __shape_type__- And we used both parent and Child methods in __shape_color__  Inheriting from builtin classes *We will create our own dictionary class*<jupyter_code>class FunDict(dict):
    """
    we will inherit our class from builtin dict method
    """
    
    def __init__(self, *args, **kwargs):
        """
        we will create a dictionary from passed arguments
        """
        print("Sending to press.")
        super().__init__(*args, **kwargs)
        print("Your dictionary is commissioned, use it wisely :)")
    
    def __getitem__(self, key):
        """
        this method is called when we want to obtain item from dictionary
        """
        print("Reading pages...")
        return super().__getitem__(key)
    
    def __setitem__(self, key, value):
        """
        this method is called when we want to create a new item
        """
        print("Searching for blank page to write...")
        ret = super().__setitem__(key, value)
        print("done")
        return ret
my_dict = FunDict(name='Hari', age=29)
my_dict
my_dict['address'] = 'kathmandu'
my_dict
my_dict['age']
my_dict['address']<jupyter_output>Reading pages...
{'fraction_non_alphanumeric': 0.11103912930811091, 'fraction_numerical': 0.006089660533817051, 'mean_word_length': 2.793120393120393, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '233625', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 2236, 'n_tokens_neox': 2178, 'n_words': 692, 'path': 'References/7.Classes-II.ipynb', 'repo_name': 'Yunika-Bajracharya/pybasics', 'size': '7718'}