Discuss Object Oriented Python ”; Previous Next Python has been an object-oriented language since it existed. In this tutorial we will try to get in-depth features of OOPS in Python programming. Print Page Previous Next Advertisements ”;
Category: object Oriented Python
Inheritance and Ploymorphism
Inheritance and Polymorphism ”; Previous Next Inheritance and polymorphism – this is a very important concept in Python. You must understand it better if you want to learn. Inheritance One of the major advantages of Object Oriented Programming is re-use. Inheritance is one of the mechanisms to achieve the same. Inheritance allows programmer to create a general or a base class first and then later extend it to more specialized class. It allows programmer to write better code. Using inheritance you can use or inherit all the data fields and methods available in your base class. Later you can add you own methods and data fields, thus inheritance provides a way to organize code, rather than rewriting it from scratch. In object-oriented terminology when class X extend class Y, then Y is called super/parent/base class and X is called subclass/child/derived class. One point to note here is that only data fields and method which are not private are accessible by child classes. Private data fields and methods are accessible only inside the class. syntax to create a derived class is − class BaseClass: Body of base class class DerivedClass(BaseClass): Body of derived class Inheriting Attributes Now look at the below example − Output We first created a class called Date and pass the object as an argument, here-object is built-in class provided by Python. Later we created another class called time and called the Date class as an argument. Through this call we get access to all the data and attributes of Date class into the Time class. Because of that when we try to get the get_date method from the Time class object tm we created earlier possible. Object.Attribute Lookup Hierarchy The instance The class Any class from which this class inherits Inheritance Examples Let’s take a closure look into the inheritance example − Let’s create couple of classes to participate in examples − Animal − Class simulate an animal Cat − Subclass of Animal Dog − Subclass of Animal In Python, constructor of class used to create an object (instance), and assign the value for the attributes. Constructor of subclasses always called to a constructor of parent class to initialize value for the attributes in the parent class, then it start assign value for its attributes. Output In the above example, we see the command attributes or methods we put in the parent class so that all subclasses or child classes will inherits that property from the parent class. If a subclass try to inherits methods or data from another subclass then it will through an error as we see when Dog class try to call swatstring() methods from that cat class, it throws an error(like AttributeError in our case). Polymorphism (“MANY SHAPES”) Polymorphism is an important feature of class definition in Python that is utilized when you have commonly named methods across classes or subclasses. This permits functions to use entities of different types at different times. So, it provides flexibility and loose coupling so that code can be extended and easily maintained over time. This allows functions to use objects of any of these polymorphic classes without needing to be aware of distinctions across the classes. Polymorphism can be carried out through inheritance, with subclasses making use of base class methods or overriding them. Let understand the concept of polymorphism with our previous inheritance example and add one common method called show_affection in both subclasses − From the example we can see, it refers to a design in which object of dissimilar type can be treated in the same manner or more specifically two or more classes with method of the same name or common interface because same method(show_affection in below example) is called with either type of objects. Output So, all animals show affections (show_affection), but they do differently. The “show_affection” behaviors is thus polymorphic in the sense that it acted differently depending on the animal. So, the abstract “animal” concept does not actually “show_affection”, but specific animals(like dogs and cats) have a concrete implementation of the action “show_affection”. Python itself have classes that are polymorphic. Example, the len() function can be used with multiple objects and all return the correct output based on the input parameter. Overriding In Python, when a subclass contains a method that overrides a method of the superclass, you can also call the superclass method by calling Super(Subclass, self).method instead of self.method. Example class Thought(object): def __init__(self): pass def message(self): print(“Thought, always come and go”) class Advice(Thought): def __init__(self): super(Advice, self).__init__() def message(self): print(”Warning: Risk is always involved when you are dealing with market!”) Inheriting the Constructor If we see from our previous inheritance example, __init__ was located in the parent class in the up ‘cause the child class dog or cat didn’t‘ve __init__ method in it. Python used the inheritance attribute lookup to find __init__ in animal class. When we created the child class, first it will look the __init__ method in the dog class, then it didn’t find it then looked into parent class Animal and found there and called that there. So as our class design became complex we may wish to initialize a instance firstly processing it through parent class constructor and then through child class constructor. Output In above example- all animals have a name and all dogs a particular breed. We called parent class constructor with super. So dog has its own __init__ but the first thing that happen is we call super. Super is built in function and it is designed to relate a class to its super class or its parent class. In this case we saying that get the super class of dog and pass the dog instance to whatever method we say here the constructor __init__. So in another words we are calling parent class Animal __init__ with the dog object. You may ask why we won’t just say Animal __init__ with the dog instance, we could do this but if the name of animal class
Building Blocks
Object Oriented Python – Building Blocks ”; Previous Next In this chapter, we will discuss object oriented terms and programming concepts in detail.Class is a just a factory for an instance. This factory contains the blueprint which describes how to make the instances. An instances or object are constructed from the class. In most cases, we can have more than one instances of a class. Every instance has a set of attribute and these attributes are defined in a class, so every instance of a particular class is expected to have the same attributes. Class Bundles : Behavior and State A class will let you bundle together the behavior and state of an object. Observe the following diagram for better understanding − The following points are worth notable when discussing class bundles − The word behavior is identical to function – it is a piece of code that does something (or implements a behavior) The word state is identical to variables – it is a place to store values within a class. When we assert a class behavior and state together, it means that a class packages functions and variables. Classes have methods and attributes In Python, creating a method defines a class behavior. The word method is the OOP name given to a function that is defined within a class. To sum up − Class functions − is synonym for methods Class variables − is synonym for name attributes. Class − a blueprint for an instance with exact behavior. Object − one of the instances of the class, perform functionality defined in the class. Type − indicates the class the instance belongs to Attribute − Any object value: object.attribute Method − a “callable attribute” defined in the class Observe the following piece of code for example − var = “Hello, John” print( type (var)) # ‘str’> or <class ”str”> print(var.upper()) # upper() method is called, HELLO, JOHN Creation and Instantiation The following code shows how to create our first class and then its instance. class MyClass(object): pass # Create first instance of MyClass this_obj = MyClass() print(this_obj) # Another instance of MyClass that_obj = MyClass() print (that_obj) Here we have created a class called MyClass and which does not do any task. The argument object in MyClass class involves class inheritance and will be discussed in later chapters. pass in the above code indicates that this block is empty, that is it is an empty class definition. Let us create an instance this_obj of MyClass() class and print it as shown − <__main__.MyClass object at 0x03B08E10> <__main__.MyClass object at 0x0369D390> Here, we have created an instance of MyClass. The hex code refers to the address where the object is being stored. Another instance is pointing to another address. Now let us define one variable inside the class MyClass() and get the variable from the instance of that class as shown in the following code − class MyClass(object): var = 9 # Create first instance of MyClass this_obj = MyClass() print(this_obj.var) # Another instance of MyClass that_obj = MyClass() print (that_obj.var) Output You can observe the following output when you execute the code given above − 9 9 As instance knows from which class it is instantiated, so when requested for an attribute from an instance, the instance looks for the attribute and the class. This is called the attribute lookup. Instance Methods A function defined in a class is called a method. An instance method requires an instance in order to call it and requires no decorator. When creating an instance method, the first parameter is always self. Though we can call it (self) by any other name, it is recommended to use self, as it is a naming convention. class MyClass(object): var = 9 def firstM(self): print(“hello, World”) obj = MyClass() print(obj.var) obj.firstM() Output You can observe the following output when you execute the code given above − 9 hello, World Note that in the above program, we defined a method with self as argument. But we cannot call the method as we have not declared any argument to it. class MyClass(object): def firstM(self): print(“hello, World”) print(self) obj = MyClass() obj.firstM() print(obj) Output You can observe the following output when you execute the code given above − hello, World <__main__.MyClass object at 0x036A8E10> <__main__.MyClass object at 0x036A8E10> Encapsulation Encapsulation is one of the fundamentals of OOP. OOP enables us to hide the complexity of the internal working of the object which is advantageous to the developer in the following ways − Simplifies and makes it easy to understand to use an object without knowing the internals. Any change can be easily manageable. Object-oriented programming relies heavily on encapsulation. The terms encapsulation and abstraction (also called data hiding) are often used as synonyms. They are nearly synonymous, as abstraction is achieved through encapsulation. Encapsulation provides us the mechanism of restricting the access to some of the object’s components, this means that the internal representation of an object can’t be seen from outside of the object definition. Access to this data is typically achieved through special methods − Getters and Setters. This data is stored in instance attributes and can be manipulated from anywhere outside the class. To secure it, that data should only be accessed using instance methods. Direct access should not be permitted. class MyClass(object): def setAge(self, num): self.age = num def getAge(self): return self.age zack = MyClass() zack.setAge(45) print(zack.getAge()) zack.setAge(“Fourty Five”) print(zack.getAge()) Output You can observe the following output when you execute the code given above − 45 Fourty Five The data should be stored only if it is correct and valid, using Exception handling constructs. As we can see above, there is no restriction on the user input to setAge() method. It could be a string, a number, or a list. So we need to check onto above code to ensure correctness of being stored. class MyClass(object): def setAge(self, num): self.age = num def getAge(self): return self.age zack = MyClass() zack.setAge(45) print(zack.getAge())
Object Oriented Shortcuts
Object Oriented Shortcuts ”; Previous Next This chapter talks in detail about various built-in functions in Python, file I/O operations and overloading concepts. Python Built-in Functions The Python interpreter has a number of functions called built-in functions that are readily available for use. In its latest version, Python contains 68 built-in functions as listed in the table given below − BUILT-IN FUNCTIONS abs() dict() help() min() setattr() all() dir() hex() next() slice() any() divmod() id() object() sorted() ascii() enumerate() input() oct() staticmethod() bin() eval() int() open() str() bool() exec() isinstance() ord() sum() bytearray() filter() issubclass() pow() super() bytes() float() iter() print() tuple() callable() format() len() property() type() chr() frozenset() list() range() vars() classmethod() getattr() locals() repr() zip() compile() globals() map() reversed() __import__() complex() hasattr() max() round() delattr() hash() memoryview() set() This section discusses some of the important functions in brief − len() function The len() function gets the length of strings, list or collections. It returns the length or number of items of an object, where object can be a string, list or a collection. >>> len([”hello”, 9 , 45.0, 24]) 4 len() function internally works like list.__len__() or tuple.__len__(). Thus, note that len() works only on objects that has a __len__() method. >>> set1 {1, 2, 3, 4} >>> set1.__len__() 4 However, in practice, we prefer len() instead of the __len__() function because of the following reasons − It is more efficient. And it is not necessary that a particular method is written to refuse access to special methods such as __len__. It is easy to maintain. It supports backward compatibility. Reversed(seq) It returns the reverse iterator. seq must be an object which has __reversed__() method or supports the sequence protocol (the __len__() method and the __getitem__() method). It is generally used in for loops when we want to loop over items from back to front. >>> normal_list = [2, 4, 5, 7, 9] >>> >>> class CustomSequence(): def __len__(self): return 5 def __getitem__(self,index): return “x{0}”.format(index) >>> class funkyback(): def __reversed__(self): return ”backwards!” >>> for seq in normal_list, CustomSequence(), funkyback(): print(”n{}: ”.format(seq.__class__.__name__), end=””) for item in reversed(seq): print(item, end=”, “) The for loop at the end prints the reversed list of a normal list, and instances of the two custom sequences. The output shows that reversed() works on all the three of them, but has a very different results when we define __reversed__. Output You can observe the following output when you execute the code given above − list: 9, 7, 5, 4, 2, CustomSequence: x4, x3, x2, x1, x0, funkyback: b, a, c, k, w, a, r, d, s, !, Enumerate The enumerate () method adds a counter to an iterable and returns the enumerate object. The syntax of enumerate () is − enumerate(iterable, start = 0) Here the second argument start is optional, and by default index starts with zero (0). >>> # Enumerate >>> names = [”Rajesh”, ”Rahul”, ”Aarav”, ”Sahil”, ”Trevor”] >>> enumerate(names) <enumerate object at 0x031D9F80> >>> list(enumerate(names)) [(0, ”Rajesh”), (1, ”Rahul”), (2, ”Aarav”), (3, ”Sahil”), (4, ”Trevor”)] >>> So enumerate() returns an iterator which yields a tuple that keeps count of the elements in the sequence passed. Since the return value is an iterator, directly accessing it is not much useful. A better approach for enumerate() is keeping count within a for loop. >>> for i, n in enumerate(names): print(”Names number: ” + str(i)) print(n) Names number: 0 Rajesh Names number: 1 Rahul Names number: 2 Aarav Names number: 3 Sahil Names number: 4 Trevor There are many other functions in the standard library, and here is another list of some more widely used functions − hasattr, getattr, setattr and delattr, which allows attributes of an object to be manipulated by their string names. all and any, which accept an iterable object and return True if all, or any, of the items evaluate to be true. nzip, which takes two or more sequences and returns a new sequence of tuples, where each tuple contains a single value from each sequence. File I/O The concept of files is associated with the term object-oriented programming. Python has wrapped the interface that operating systems provided in abstraction that allows us to work with file objects. The open() built-in function is used to open a file and return a file object. It is the most commonly used function with two arguments − open(filename, mode) The open() function calls two argument, first is the filename and second is the mode. Here mode can be ‘r’ for read only mode, ‘w’ for only writing (an existing file with the same name will be erased), and ‘a’ opens the file for appending, any data written to the file is automatically added to the end. ‘r+’ opens the file for both reading and writing. The default mode is read only. On windows, ‘b’ appended to the mode opens the file in binary mode, so there are also modes like ‘rb’, ‘wb’ and ‘r+b’. >>> text = ”This is the first line” >>> file = open(”datawork”,”w”) >>> file.write(text) 22 >>> file.close() In some cases, we just want to append to the existing file rather then over-writing it, for that we could supply the value ‘a’ as a mode argument, to append to the end of the file, rather than completely overwriting existing file contents. >>> f = open(”datawork”,”a”) >>> text1 = ” This is second line” >>> f.write(text1) 20 >>> f.close() Once a file is opened for reading, we can call the read, readline, or readlines method to get the contents of the file. The read method returns the entire contents of the file as a str or bytes object, depending on whether the second argument is ‘b’. For readability, and to avoid reading a large file in one go, it is often better to use a for loop directly on a file object. For text files, it will read each line, one at a time, and we can process it inside the loop body. For binary files however
Data Structures
Object Oriented Python – Data Structures ”; Previous Next Python data structures are very intuitive from a syntax point of view and they offer a large choice of operations. You need to choose Python data structure depending on what the data involves, if it needs to be modified, or if it is a fixed data and what access type is required, such as at the beginning/end/random etc. Lists A List represents the most versatile type of data structure in Python. A list is a container which holds comma-separated values (items or elements) between square brackets. Lists are helpful when we want to work with multiple related values. As lists keep data together, we can perform the same methods and operations on multiple values at once. Lists indices start from zero and unlike strings, lists are mutable. Data Structure – List >>> >>> # Any Empty List >>> empty_list = [] >>> >>> # A list of String >>> str_list = [”Life”, ”Is”, ”Beautiful”] >>> # A list of Integers >>> int_list = [1, 4, 5, 9, 18] >>> >>> #Mixed items list >>> mixed_list = [”This”, 9, ”is”, 18, 45.9, ”a”, 54, ”mixed”, 99, ”list”] >>> # To print the list >>> >>> print(empty_list) [] >>> print(str_list) [”Life”, ”Is”, ”Beautiful”] >>> print(type(str_list)) <class ”list”> >>> print(int_list) [1, 4, 5, 9, 18] >>> print(mixed_list) [”This”, 9, ”is”, 18, 45.9, ”a”, 54, ”mixed”, 99, ”list”] Accessing Items in Python List Each item of a list is assigned a number – that is the index or position of that number.Indexing always start from zero, the second index is one and so forth. To access items in a list, we can use these index numbers within a square bracket. Observe the following code for example − >>> mixed_list = [”This”, 9, ”is”, 18, 45.9, ”a”, 54, ”mixed”, 99, ”list”] >>> >>> # To access the First Item of the list >>> mixed_list[0] ”This” >>> # To access the 4th item >>> mixed_list[3] 18 >>> # To access the last item of the list >>> mixed_list[-1] ”list” Empty Objects Empty Objects are the simplest and most basic Python built-in types. We have used them multiple times without noticing and have extended it to every class we have created. The main purpose to write an empty class is to block something for time being and later extend and add a behavior to it. To add a behavior to a class means to replace a data structure with an object and change all references to it. So it is important to check the data, whether it is an object in disguise, before you create anything. Observe the following code for better understanding: >>> #Empty objects >>> >>> obj = object() >>> obj.x = 9 Traceback (most recent call last): File “<pyshell#3>”, line 1, in <module> obj.x = 9 AttributeError: ”object” object has no attribute ”x” So from above, we can see it’s not possible to set any attributes on an object that was instantiated directly. When Python allows an object to have arbitrary attributes, it takes a certain amount of system memory to keep track of what attributes each object has, for storing both the attribute name and its value. Even if no attributes are stored, a certain amount of memory is allocated for potential new attributes. So Python disables arbitrary properties on object and several other built-ins, by default. >>> # Empty Objects >>> >>> class EmpObject: pass >>> obj = EmpObject() >>> obj.x = ”Hello, World!” >>> obj.x ”Hello, World!” Hence, if we want to group properties together, we could store them in an empty object as shown in the code above. However, this method is not always suggested. Remember that classes and objects should only be used when you want to specify both data and behaviors. Tuples Tuples are similar to lists and can store elements. However, they are immutable, so we cannot add, remove or replace objects. The primary benefits tuple provides because of its immutability is that we can use them as keys in dictionaries, or in other locations where an object requires a hash value. Tuples are used to store data, and not behavior. In case you require behavior to manipulate a tuple, you need to pass the tuple into a function(or method on another object) that performs the action. As tuple can act as a dictionary key, the stored values are different from each other. We can create a tuple by separating the values with a comma. Tuples are wrapped in parentheses but not mandatory. The following code shows two identical assignments . >>> stock1 = ”MSFT”, 95.00, 97.45, 92.45 >>> stock2 = (”MSFT”, 95.00, 97.45, 92.45) >>> type (stock1) <class ”tuple”> >>> type(stock2) <class ”tuple”> >>> stock1 == stock2 True >>> Defining a Tuple Tuples are very similar to list except that the whole set of elements are enclosed in parentheses instead of square brackets. Just like when you slice a list, you get a new list and when you slice a tuple, you get a new tuple. >>> tupl = (”Tuple”,”is”, ”an”,”IMMUTABLE”, ”list”) >>> tupl (”Tuple”, ”is”, ”an”, ”IMMUTABLE”, ”list”) >>> tupl[0] ”Tuple” >>> tupl[-1] ”list” >>> tupl[1:3] (”is”, ”an”) Python Tuple Methods The following code shows the methods in Python tuples − >>> tupl (”Tuple”, ”is”, ”an”, ”IMMUTABLE”, ”list”) >>> tupl.append(”new”) Traceback (most recent call last): File “<pyshell#148>”, line 1, in <module> tupl.append(”new”) AttributeError: ”tuple” object has no attribute ”append” >>> tupl.remove(”is”) Traceback (most recent call last): File “<pyshell#149>”, line 1, in <module> tupl.remove(”is”) AttributeError: ”tuple” object has no attribute ”remove” >>> tupl.index(”list”) 4 >>> tupl.index(”new”) Traceback (most recent call last): File “<pyshell#151>”, line 1, in <module> tupl.index(”new”) ValueError: tuple.index(x): x not in tuple >>> “is” in tupl True >>> tupl.count(”is”) 1 From the code shown above, we can understand that tuples are immutable and hence − You cannot add elements to a tuple. You cannot append or extend a method. You cannot remove elements from a tuple. Tuples have no remove or
Exception and Exception Classes ”; Previous Next In general, an exception is any unusual condition. Exception usually indicates errors but sometimes they intentionally puts in the program, in cases like terminating a procedure early or recovering from a resource shortage. There are number of built-in exceptions, which indicate conditions like reading past the end of a file, or dividing by zero. We can define our own exceptions called custom exception. Exception handling enables you handle errors gracefully and do something meaningful about it. Exception handling has two components: “throwing” and ‘catching’. Identifying Exception (Errors) Every error occurs in Python result an exception which will an error condition identified by its error type. >>> #Exception >>> 1/0 Traceback (most recent call last): File “<pyshell#2>”, line 1, in <module> 1/0 ZeroDivisionError: division by zero >>> >>> var = 20 >>> print(ver) Traceback (most recent call last): File “<pyshell#5>”, line 1, in <module> print(ver) NameError: name ”ver” is not defined >>> #Above as we have misspelled a variable name so we get an NameError. >>> >>> print(”hello) SyntaxError: EOL while scanning string literal >>> #Above we have not closed the quote in a string, so we get SyntaxError. >>> >>> #Below we are asking for a key, that doen”t exists. >>> mydict = {} >>> mydict[”x”] Traceback (most recent call last): File “<pyshell#15>”, line 1, in <module> mydict[”x”] KeyError: ”x” >>> #Above keyError >>> >>> #Below asking for a index that didn”t exist in a list. >>> mylist = [1,2,3,4] >>> mylist[5] Traceback (most recent call last): File “<pyshell#20>”, line 1, in <module> mylist[5] IndexError: list index out of range >>> #Above, index out of range, raised IndexError. Catching/Trapping Exception When something unusual occurs in your program and you wish to handle it using the exception mechanism, you ‘throw an exception’. The keywords try and except are used to catch exceptions. Whenever an error occurs within a try block, Python looks for a matching except block to handle it. If there is one, execution jumps there. syntax try: #write some code #that might throw some exception except <ExceptionType>: # Exception handler, alert the user The code within the try clause will be executed statement by statement. If an exception occurs, the rest of the try block will be skipped and the except clause will be executed. try: some statement here except: exception handling Let’s write some code to see what happens when you not use any error handling mechanism in your program. number = int(input(”Please enter the number between 1 & 10: ”)) print(”You have entered number”,number) Above programme will work correctly as long as the user enters a number, but what happens if the users try to puts some other data type(like a string or a list). Please enter the number between 1 > 10: ”Hi” Traceback (most recent call last): File “C:/Python/Python361/exception2.py”, line 1, in <module> number = int(input(”Please enter the number between 1 & 10: ”)) ValueError: invalid literal for int() with base 10: “”Hi”” Now ValueError is an exception type. Let’s try to rewrite the above code with exception handling. import sys print(”Previous code with exception handling”) try: number = int(input(”Enter number between 1 > 10: ”)) except(ValueError): print(”Error..numbers only”) sys.exit() print(”You have entered number: ”,number) If we run the program, and enter a string (instead of a number), we can see that we get a different result. Previous code with exception handling Enter number between 1 > 10: ”Hi” Error..numbers only Raising Exceptions To raise your exceptions from your own methods you need to use raise keyword like this raise ExceptionClass(‘Some Text Here’) Let’s take an example def enterAge(age): if age<0: raise ValueError(”Only positive integers are allowed”) if age % 2 ==0: print(”Entered Age is even”) else: print(”Entered Age is odd”) try: num = int(input(”Enter your age: ”)) enterAge(num) except ValueError: print(”Only positive integers are allowed”) Run the program and enter positive integer. Expected Output Enter your age: 12 Entered Age is even But when we try to enter a negative number we get, Expected Output Enter your age: -2 Only positive integers are allowed Creating Custom exception class You can create a custom exception class by Extending BaseException class or subclass of BaseException. From above diagram we can see most of the exception classes in Python extends from the BaseException class. You can derive your own exception class from BaseException class or from its subclass. Create a new file called NegativeNumberException.py and write the following code. class NegativeNumberException(RuntimeError): def __init__(self, age): super().__init__() self.age = age Above code creates a new exception class named NegativeNumberException, which consists of only constructor which call parent class constructor using super()__init__() and sets the age. Now to create your own custom exception class, will write some code and import the new exception class. from NegativeNumberException import NegativeNumberException def enterage(age): if age < 0: raise NegativeNumberException(”Only positive integers are allowed”) if age % 2 == 0: print(”Age is Even”) else: print(”Age is Odd”) try: num = int(input(”Enter your age: ”)) enterage(num) except NegativeNumberException: print(”Only positive integers are allowed”) except: print(”Something is wrong”) Output Enter your age: -2 Only positive integers are allowed Another way to create a custom Exception class. class customException(Exception): def __init__(self, value): self.parameter = value def __str__(self): return repr(self.parameter) try: raise customException(”My Useful Error Message!”) except customException as instance: print(”Caught: ” + instance.parameter) Output Caught: My Useful Error Message! Exception hierarchy The class hierarchy for built-in exceptions is − +– SystemExit +– KeyboardInterrupt +– GeneratorExit +– Exception +– StopIteration +– StopAsyncIteration +– ArithmeticError | +– FloatingPointError | +– OverflowError | +– ZeroDivisionError +– AssertionError +– AttributeError +– BufferError +– EOFError +– ImportError +– LookupError | +– IndexError | +– KeyError +– MemoryError +– NameError | +– UnboundLocalError +– OSError | +– BlockingIOError | +– ChildProcessError | +– ConnectionError | | +– BrokenPipeError | | +– ConnectionAbortedError | | +– ConnectionRefusedError | | +– ConnectionResetError | +– FileExistsError | +– FileNotFoundError | +– InterruptedError | +– IsADirectoryError | +– NotADirectoryError | +– PermissionError | +– ProcessLookupError |