So guys, let us start this object oriented programming in python with the fundamentals in this post. Thus, we discuss the abstract data types first.
Abstract Data Types
- An abstract data type is a set of objects and the operations on those objects.
- These are bound together so that one can pass an object from one part of a program to another, and in doing so provide access not only to the data attributes of the object but also to operations that make it easy to manipulate that data.
- The specifications of those operations define an interface between the abstract data type and the rest of the program.
- In Python, one implements data abstractions using classes.
Hence, as in other programming languages, in Python also we use classes as an abstract data type. Let’s dig in to classes then.
- In Python, class declarations are very similar to function declarations, a header line with the appropriate keyword followed by a suite as its definition, as indicated above.
- The biggest difference between function and classes is that you run functions but create objects with classes.
- Just remember to keep in mind that even though classes are objects (everything in Python is an object), they are not realizations of the objects that they are defining.
- When you create a class, you are practically creating your own kind of data type. All instances of that class are similar, but classes differ from one another.
Classes: Declaration and Definition of Data Attributes & Methods
- As with Python functions, there is no distinction between declaring and defining classes because they occur simultaneously, i.e., the definition (the class suite) immediately follows the declaration (header line with the class keyword) and the always recommended, but optional, documentation string.
- Likewise, all methods must also be defined at this time.
- Moreover, Python doesn’t support pure virtual function (like C++) and abstract methods (like JAVA), but instead we can raise a NotImplementedError exception to get the same effect.
- When defining data attributes python treats them simply as variables of the class in which we are defining them.
- They can be used like any other variable in that they are set when the class is created and can be updated either by methods within the class or elsewhere in the main part of the program.
We can see that the data attribute foo is just used like a normal variable or object of Python in print statement
- Similarly, a method, such as the myNoActionMethod method of the MyClass class in the example below, is simply a function defined as part of a class definition.
In the example above, we have pass in the body of the method myNoActionMethod, it is one of the keywords of Python. It is used when a statement is required syntactically but you do not want any command or code to execute. The pass statement is a null operation; nothing happens when it executes. The pass is also useful in places where your code will eventually go, but has not been written yet.
Just like pass, you’d be also seeing object keyword being used at the time of definition of class. Writing object in the definition of class like class MyClass(object) denotes that we have created a new class named MyClass which is inheriting the object class which is the superclass of all the classes in Python. This is pretty much analogous to Java where everything inherits the object class or you can say that object class is the base class of all the classes that are defined by the user.
Classes: Instance Creation
- Many other Object Oriented languages provide a new keyword to create an instance of a class. Python’s approach is much simpler.
- Once a class has been defined, creating an instance is no more difficult than calling a function literally. For example,
- When the class is invoked, the first step in the instantiation process is to create the instance object.
- Once the object is available, Python checks if an __init__() method has been implemented.
- By default, no special actions are enacted without definition of __init__() method. If any special actions need to be enacted then the programmer need to implement __init__() in order to override its default behavior.
- If __init__() has not been implemented, the object is then returned and the instantiation process is complete.
- However, if __init__() has been implemented, then that special method is invoked and the instance object passed in as the first argument (self), just like a standard method call.
- Any arguments passed to the class invocation call are passed on to __init__(). You can practically envision the call to create the instance as a call to the constructor.
- In summary,
- You do not call new to create an instance, and you do not define a constructor: Python creates the object for you; and
- __init__(), is simply the first method that is called after the interpreter creates an instance for you in case you want to prep the object a little bit more before putting it to use.
- Python users have the ability to subclass built-in types, and so there needed to be a way to instantiate immutable objects, e.g., subclassing strings, numbers, etc.
- In such cases, the interpreter calls __new__(), a static method, with the class and passing in the arguments made in the class instantiation call.
- It is the responsibility of __new__() to call a superclass __new__() to create the object.
- __init__() vs. __new__():
- __init__() has to return a valid instance so that the interpreter can then call __init__() with that instance as self.
- While calling a superclass __new__() to create the object is just like using a new keyword to create an object in other languages.
- It is special method to destruct an object.
- However, due to the way Python manages garbage collection of objects (by reference counting), this function is not executed until all references to an instance object have been removed.
- Destructors in Python are methods that provide special processing before instances are deallocated and are not commonly implemented since instances are seldom deallocated explicitly.
Let us consider an example below, such that it indicates when an instance of class is created as well as the instance destroyed.
Click here to Copy the Code
In this example, what we do is that we are first creating an instance pt1 of class Point, then we create two more objects pt2 and pt3 which are nothing but the aliases of pt1 as we have directly assigned pt1 to pt2 as well as pt3. Also, in the output below, we can see that the id of pt2 and pt3 is same as pt1, which validates that pt2 and pt3 are not instances. In fact, we have only initialized one instance of class Point which is pt1.
The output, also shows that even though we have called del on all the three pt1, pt2 and pt3, it only prints “destroyed” for once which means that it detects that there was only one instance and destroys it.
Class Attributes vs Instance Attributes
- An attribute defined in the class, either textually in a class definition or later by assignment to an attribute reference of the class, is a class attribute. It is stored in the class’s name space.
- An attribute defined in the instance, by assignment, is an instance attribute and is stored in the instance’s name space — even if there was a class attribute with the same name! Assignment via the instance results in an instance attribute that shadows the class attribute.
- Class attributes are more persistent. Instance attributes come and go. Also, if a new instance is created after a class attribute has been modified, the updated value will be reflected across all the instances.
- Class attributes are simply data values associated with a class and not any particular instances like instance attributes are. We can say that class attributes are static and instance attribute are automatic.
We will take an example to go through this topic and understand it. Before proceeding, you have to recall that in Python, the class object is at the top of the hierarchy i.e. everything derives it.
Click here to Copy the Code
Following is the output of the above example
- In the example above, __str__() is used, this method is used for informal string representation of an object. (you can say that it converts object type to str type).
- There is one more special function __lt__() in the example taken above, it is equivalent to ‘<‘, we can also say that it operator overloaded version of ‘<‘, it is there to check whether the string on left side of ‘<’ is less than the string on right side of ‘<’.
The effect above mentioned __str__and __lt__ methods is clearly visible in the output above. Moreover, the first two lines of the output clearly demonstrates the concept of inheritance and in the last two lines we can see how does multi-level inheritance works.
The code used for example, is pretty-much self explanatory given the usage of docstrings and comments. Although, if there are any doubts or queries or confusions regarding the code, then you can mention it in comments to this blog post.
Finally, before concluding this post, we will just shed light on encapsulation & information hiding of object oriented programming in python.
Encapsulation & Information Hiding
- Python provides encapsulation but doesn’t provide information hiding.
we can use dot notation to access attributes such as Rafael’s age and identification number.
- Why No Information Hiding:
- There is no way for the implementer of a class to restrict access to the attributes of class instances. For example, a client of a Person can write the expression Rafael.lastName rather than Rafael.getLastName().
So, guys that was all the basics about Object Oriented Programming in Python and we can say that because of absence of encapsulation & information hiding, Python is not a purely Object-Oriented Programming Language. In my next post, I will discuss on a special topic named Method Resolution Order in Python. Meanwhile, you all can refer a special post on Searching Techniques in Python, posted by my friend Kaushik Vaghani.