This in-depth tutorial explains what are Python Classes And Objects and related concepts like methods, attributes, modifiers, etc with examples:
Object-oriented programming (OOP) can be seen as a method of encapsulating related properties and behaviors using a special construct called classes into single entities called objects.
When we hear of OOP, the first thing that comes to mind is Classes and Objects. Before we delve into what these are, let’s see a real-life example. Imagine an architect who creates a blueprint of a house that shows the dimensions, structure, number of rooms, and other details of the house.
=> Check Here To See A-Z Of Python Training Tutorials
Table of Contents:
Python Classes And Objects
We can think of the blueprint as a class, and the house attributes to be the doors, windows, roof, walls, floor, etc, and a house can have the following actions such as opening/closing the door and window, shielding from the sun, etc.
Each time a new house is built from this blueprint, we are actually creating an instance of the class. Now, each house can have different values for its attributes like the color of the walls, the thickness of the doors and windows, etc.
In this tutorial, we shall delve into Python Classes and Objects.
What Is Object-Oriented Programming
OOP permits us to bundle similar properties and behaviors into containers. In Python, these containers are called Classes. A class presents to the real-world an instance of itself called Objects.
OOP was designed to address some important principles like Modularity, Abstraction, and Encapsulation. Let’s have a look at what each of these means.
Modularity
Modern software commonly consists of several different components that have unique responsibilities and interact with each order for the proper functionality of the entire system.
Modularity in OOP refers to grouping components with related functionality into a single unit. This helps in robustness, readability, and reusability.
As a real-world example, a mobile phone can be seen as a single unit. But it can perform several functionalities like play music, dialing, messaging, calling, etc. Each of these functionalities is a component that is designed and maintained separately, however they are connected together for the proper functionality of the mobile phone.
This way, robustness is achieved as each component can be tested, debugged, and maintained separately. Reusability in this case is easy as these components can be used when a related need arises.
In Python, we have modules that are collections of functions, classes, and variables defined in a single file and are closely related. For example, the sys module provides functions and variables that interact with the interpreter.
Abstraction
Abstraction is the process of hiding a method’s real implementation and only exposing its required characteristics and behavior.
As a real-world example, let’s consider our mobile phones. In order to make a call, we dial the number or select a contact and press the call button. All we know is that a call is made but we don’t know and we may not care about all the background processes that take place for the call to be successful.
In Python, the ABC class of the abc module can be extended to define abstract methods with the abstract method decorator. Then any class inherited from this class must implement those abstract methods.
Encapsulation
Encapsulation in OOP allows programmers to restrict access to chosen components (attributes and methods) from being accidentally accessed or depended upon.
This gives a programmer the freedom to implement these components without the concern that other programmers will write code that depends on them.
In Python, it is loosely supported by adding a double underscore(__) before the component so that it is made private. This actually prevents it from being accidentally accessed, but we shall see later in this tutorial that nothing is really private in Python.
Class Definition
In OOP, a class serves as a blueprint for its instances (objects), as such, is the primary means for abstraction. The class provides a set of behavior in the form of methods and attributes. The methods and attributes are common to all instances of that class.
Syntax
class <class name> : // class body
The class construct starts with the keyword, class, followed by the name of the class, a colon(:), and then an indented block of code also known as the body of the class.
Consider the below diagram of a class that prints a string.
Example 1: Define a class that can add and subtract two numbers.
Following the syntax above, we shall decide on the name to give our class. Then in the class body, we shall define two methods namely i.e. add and subtract. Each of these methods will implement the operation for addition and subtraction respectively.
class add_sub: def __init__(self, x, y): self.x = x self.y = y # define 'add' method def add(self): return self.x + self.y # define 'subtract' method def subtract(self): return self.x - self.y if __name__ == '__main__': x = 10 y = 6 # create an instance opp = add_sub(x,y) # call add method print(f'{x} + {y} = {opp.add()}') #print(opp.add()) # call subtract method print(f'{x} - {y} = {opp.subtract()}')
Output
Few things to note from the example above are:
- The __init__ method is reserved in Python, also known as a constructor, it is called each time when an object or instance is created from a class. It is commonly used to initialize the attributes of the class for the created object.
- Each method of the class has the self-identifier, which identifies the instance upon which a method is invoked. This allows us to create as many objects of a class as we wish.
Class Objects
We already saw that a class is a blueprint. So, objects also known as instances are known to be the realization of the blueprint, containing actual values.
In example 1 above, the line of code
opp = add_sub(x,y)
Creates an object of the class and passes two variables to it; x and y. Under the hood, this will call the __init__ reserved methods which will receive these parameters and initialize the class’s variables. Through this object, we can access the class’s methods and public variables.
Thanks to the self-identifier, as we are able to create as many objects of a class as we wish. It is important to note that the self-identifier doesn’t necessarily need to be called self. It can be anything, as long as it appears as the first parameter in any class method.
Class Methods and Attributes
The building block of a class is its attributes and methods. In this section, we’ll see what class methods, class attributes, and instance method, instance attributes are and get to know how to use them.
Class and Instance Attributes
In class, we can create both class attributes and instance attributes. But what are these attributes? Let’s consider the example below to know more.
Example 2: Define class and instance attributes
class Cylinder: # class attribute pi = 3.14 def __init__(self, radius, height): # instance variables self.radius = radius self.height = height if __name__ == '__main__': c1 = Cylinder(4, 20) c2 = Cylinder(10, 50) print(f'Pi for c1:{c1.pi} and c2:{c2.pi}') print(f'Radius for c1:{c1.radius} and c2:{c2.radius}') print(f'Height for c1:{c1.height} and c2:{c2.height}') print(f'Pi for both c1 and c2 is: {Cylinder.pi}')
Output
The class attribute is defined at the level of the class while the instance attributes are instantiated in the special method(__init__) where they are attached to the self identifier.
Class attributes are mostly used to set a constant or default value that will be used in all instances of the class while the instance attributes are meant to be specific for each instance of the class.
In example 2 above, we can access the class attributes just in the same way we can access instance attributes. However, we can also access class attributes by accessing the class directly. Check the last print in example 2.
Class and Instance Methods
What are methods? They are basically, functions but are defined in a class. To know more about functions, check out the tutorial on Python Functions.
We can define many types of methods in a class. We have the class method that can be accessed directly from the class and we have the instance method that can only be accessed via an instance of the class. Consider the example below.
Example 3: Define class and instance methods
As a continuation of example 2, let’s add instance and class methods to our Cylinder class.
class Cylinder: # class attribute pi = 3.14 def __init__(self, radius, height): # instance variables self.radius = radius self.height = height # instance method def volume(self): return Cylinder.pi * self.radius**2 * self.height # class method @classmethod def description(cls): return f'This is a Cylinder class that computes the volume using Pi={cls.pi}' if __name__ == '__main__': c1 = Cylinder(4, 2) # create an instance/object print(f'Volume of Cylinder: {c1.volume()}') # access instance method print(Cylinder.description()) # access class method via class print(c1.description()) # access class method via instance
Output
The method volume is an instance method. It takes a compulsory first parameter(self identifier) and accesses the class instance attribute with this self-identifier. This method is accessed only through instances of the class.
The method description is a class method. Unlike instance methods, this method is bound to the class and not its instance. They also take a compulsory first parameter which instead points to the class and not the object instance. In Python, the @classmethod decorator is used to create these class methods.
Before we end this section, we should also note that we have static methods which are created using the @staticmethod decorator. These methods do not receive an implicit first argument and can be called from the class and instance.
Access Modifiers (Private, Public, and Protected)
In Python and other programming languages like C++ and Java, access modifiers are used to restrict access to a class’s attributes and methods. This is an extra security level that has unauthorized access and modification.
Python has three types of access modifiers. In this section, we shall explore each of them.
Public Access Modifier
A Public access modifier renders a class’s attributes and methods easily accessible from any part of the program. There is no special treatment to give a class attribute and method to make it public since they are public by default.
In example 2, we could access the Cylinder’s attributes; pi, radius, and height outside the class. Also, in example 3, we could access the class Cylinder’s methods; volume, and description outside its class. This is possible and thanks to the public access modifier.
Protected Access Modifier
A Protected access modifier renders a class’s attributes and methods only accessible to a class derived from it. Here, we need special treatment of adding a single underscore ‘_’ before the attribute or method name to make it protected.
Example 4: Render a class’s attributes and methods protected.
Here, we shall introduce a new concept called inheritance which we shall discuss more in the section below.
class Article: def __init__(self, title, page_count): # initialize protected attributes self._title = title self._page_count = page_count # define protected method def _show(self): # access protected attributes inside class print("Article Title: ", self._title) print("Page Count: ", self._page_count) class Author(Article): def __init__(self, name, title, page_count): Article.__init__(self, title, page_count) self.name = name def display(self): print("Author Name: ", self.name) # access Article's protected method self._show() print("------------------ \n") author = Author("Eyong Kevin", "Python Classes and Objects", 3000) author.display() # access protected data print(author._title)
Output
From the example code above, we see some protected variables and methods in the Article class. This class is then derived in the Author class which can freely access these attributes.
However, the last line of code print(author._title) tells us something different. We are able to access these protected attributes in the class from where it was derived too.
Just like the private access modifier which we shall see later, there is no clear restriction provided by the protected access modifier. This is just a convention in Python that, any attributes preceding with an underscore should be treated as protected.
Private Access Modifier
A Private access modifier renders a class’s attributes and methods only accessible within its class. In Python, It is required to add a double underscore ‘__’ symbol before the attributes to make it private.
Example 5: Render a class’s attributes and methods private.
class Language: # private class attribute __country = "Cameroon" def __init__(self, name): # initialize private instance attribute self.__name = name # private method def __show(self): # access private attributes print("Country: ", Language.__country) print("Name :", self.__name) # public method def display(self): # access private method within class self.__show() lang = Language("English") lang.display() # access private variable and method lang._Language__show() print("Access Private Name: ",lang._Language__name)
Output
The line of code lang._Language__show() shows how we can access a private attribute outside its class.
So, we should understand that the terms Private and Protected are just conversions in Python, and there is no real restriction as it is claimed.
Class And Instance Namespaces
A namespace is a collection of the currently defined objects. Python maintains a namespace in a dictionary-like structure where the object’s name represents the key and the object itself represents the value. In the world of classes and objects, we have Instance and Class Namespaces.
The Instance Namespace is a collection of the currently defined attributes specific to an individual instance of a class. In example 2 above, all attributes i.e. radius, height, volume() that can be accessed via the class instance makes up the instance namespace.
The Class Namespace is a collection of the currently defined attributes that are not related to any particular instance. They are to be shared by all instances of the class. In example 2, we saw that the class attribute pi and method description() could be accessed by referencing the class itself.
We can see in example 2 that we can access attributes from the class Namespace(class attributes) through the class instance but we can’t access instance namespace(instance attributes) through the class itself.
Class Inheritance
In modern software development, it is common to organize various structural components of the software in a hierarchical fashion. This organization is done in a level-by-level manner with an “is a” relationship between levels.
The diagram below shows inheritance in the world of vehicles.
This hierarchical fashion that groups general components at the top level while more specific components are grouped in lower levels is known as inheritance.
In Python, the terms parent class, and base class are used to represent top-level components while subclass and child class are used to represent lower-level components.
A parent class is commonly general while the child class either specializes in an existing behavior by either overriding an existing method of the parent or extending the method.
Before we look into a simple example, note that in Python there are four types of inheritance.
- Single
- Multiple
- Multilevel
- Hierarchical
Example 6: Demonstrate the inheritance between a child and parent.
In this example, we shall look at the first type(single) and we shall cover method overriding, and the super() function.
class Parent(): def __init__(self, hair_color, temper): self.hair_color = hair_color self.temper = temper def sleeping_style(self): print('big fan of nap') class Child(Parent): def __init__(self, hair_color, temper): # call the base class init function super().__init__(hair_color, temper ) def sleeping_style(self): # extending the base class method super().sleeping_style() print('tossing and turning') if __name__ == '__main__': # create instance of the child class enow = Child('black', 'slow in anger') enow.sleeping_style() print(enow.hair_color) print(enow.temper)
Output
In the above example, we have two classes, a Parent class, and a Child class. Through inheritance, the Child class inherits some of the attributes of the Parent class like hair color, temper, and even sleeping style. Notice how the Parent class is passed as an argument to the Child class.
In the Child class, we use the super() function to access the Parent class attributes. This function was called twice, once in the __init__() function, and passed to parameters to initialize the Parent class attributes. It was called again in the sleeping_style() method and extended with more sleeping styles.
The Child class doesn’t define attributes for hair color and temper but inherits them from the Parent class where it was initialized.
Polymorphism With Inheritance
In Python, Polymorphism allows us to override or extend methods of the Parent class in the Child class. In this case, we define methods in the Child class that have the same name as the methods in the Parent class.
This is common when a method inherited from the Parent class doesn’t satisfy the Child class. Another popular terminology used here is Method Overriding.
In example 6 above, we have the method sleeping_style() that appears both in the Parent and Child class. Since the Child’s sleeping style is more than just napping, there is also tossing and turning. In order to satisfy the Child class, we have to override the Parent class.
Frequently Asked Questions
Q #1) What are the types of inheritance in Python?
Answer: In Python, we have four types of inheritance namely Single, Multiple, Multilevel, and Hierarchical.
Q #2) What is super()__init__ in Python?
Answer: The super() function enables us to access the immediate parent’s attributes from the child. So, super().__init__ will call the parent’s __init__ method in the child class.
Q #3) What does self mean in Python?
Answer: self – which also known as self identifier identifies the instance upon which a method or attribute is invoked. It permits us to create multiple instances of the same class and each instance will maintain its own instance variables to reflect its current state.
Q #4) What are Polymorphic Functions?
Answer: In Python, a function that can accept arguments of different types is known as a Polymorphic function.
For example the print() function below
>>> print(3) # takes in an integer 3 >>> print("Hello world") # takes in a string Hello world >>> print([4,5,3]) # takes in a list [4, 5, 3]
Q #5) How do you call a class in Python?
Answer: In Python, we call a class by its name, followed by open and close parentheses in which we either give it some arguments that will be accepted in its __init__ method. This process creates an instance of the class.
Q #6) Why __ is used in Python?
Answer: In Python, we use the double underscore (__) before an attribute name to make it private. Also, we can use it or the single underscore (_) after an attribute name to avoid name clashes with reserved keywords. For example, class__
Conclusion
In this tutorial, we took a look at what Classes and Objects are in OOP.
We looked at the importance of OOP and examined the components of a class such as variables and methods under which we treated access modifiers like Private, Public, and Protected.
Lastly, we looked at the two very important concepts of OOP i.e. Inheritance and Polymorphism.
=> Read Through The Easy Python Training Series