Basic OOPs Concepts in Python
In this post, we introduce the basics of object-oriented programming in Python. We discuss how to create objects and classes, variables, methods, and the difference between instance methods and static methods.
Procedural Programming vs Object-Oriented Programming
Python is an extremely versatile language that can be used for a variety of tasks such as software engineering, web development, scripting, data analysis, or machine learning. Especially those who write automation scripts or do data analysis are more likely to take a procedural programming approach. They usually structure their Python code as a sequence of blocks and functions without ever making use of Python’s object-oriented features. They understandably wonder whether Python is an object-oriented language at all.
Is Python an object oriented language?
Python has been object-oriented from its inception. Object-oriented Python especially shines when you have to model complicated data types and their relationships that go beyond primitive data types such as strings and integers. Python is often used in scenarios when procedural programming is more appropriate. However, learning how to program in an object-oriented manner will not only make your code cleaner, more reusable, expressive, and generally more ready for production use.
What is a Class in Python?
Classes are the basic building blocks of object-oriented programs. They can be used to define custom data types and real-world objects. A class is essentially a blueprint according to which objects are created. Let’s say you want to model a bike object and you need to store its color and the number of gears. We can create a class “Bike” with color and gears as variables.
class Bike: def __init__(self, color, gears): self.color = color self.gears = gears
The creation of a class requires a Python class constructor, which is a function named __init__ which takes all the variables that the class will contain as arguments in addition to the keyword self.
What is Self in Python?
In Python self refers to the instance of the class. In the Python class constructor, we pass two variables. These variables are only local to the constructor method. Outside the init call you cannot use them and once the call has executed the variables will disappear. Therefore we assign them to instance variables of the same name.
By prefixing self to the variable names, we tell Python that gears and color are attached to the class instance and not just to the method. They will persist for the lifetime of the class. By using self, you are pointing Python to a particular instance of the class you are using. The constructor, and other instance methods for that matter, manipulate an instance’s state. But they do not automatically receive the instance which is why you need to explicitly pass “self” as the first method argument.
A Note on Indentation in Python
Contrary to other languages that use square brackets, in Python indentation is used to communicate hierarchies in the code. You can set an indentation level in your IDE. Most people use 3-4 spaces. Let’s say you use an indentation level of 3 spaces. Whenever you write a method inside a class, the method names need to be indented by 3 spaces in relation to the class declaration. Code in a method is indented 3 spaces in relation to the method definition, etc.
Creating Objects and Using Methods in Python
To instantiate an object in Python you simply declare a variable of the desired object type.
# bike = Bike()
If you are in a Jupyter notebook where code is executed sequentially, you can instantiate the class right after its declaration. I’m working out of the IDE PyCharm here. To be able to execute this code I need to declare a Python main function, which serves as an entry point to a Python program.
if __name__ == '__main__': bike = Bike()
This will result in an error, though, since we specified in the class declaration that the constructor takes 2 additional arguments besides self.
if __name__ == '__main__': #bike = Bike() bike = Bike("red", 3)
Method vs Function
If you come from a procedural programming background, you are probably familiar with functions such as this one:
def hello_world(): return "Hello World"
Usually, a Python function returns a value after manipulating some data you pass to it directly via the method call.
You define a Python method as a function outside the scope of a class. In fact, a method is nothing but a function that is called on the instance of an object. Accordingly, a method can also make use of variables belonging to the class instance, even if they are not explicitly passed in the method call. In our bike class, we can add two methods to shift gears upwards by a certain number and to return the current gear. In both cases, we are making use of the instance variable gear.
class Bike: def __init__(self, color, current_gear): self.color = color self.gear = current_gear def increment_gear_by(self, gears): self.gear += gears def get_current_gear(self): return self.gear
Instance Variables
The instance variables preceded by self can also be called from outside the class after an instance of that class has been created
if __name__ == '__main__': #bike = Bike() bike = Bike("red", 3) print(bike.color)
This will print “red” to the console.
Python Private Method
In other languages like Java you can explicitly define a method as private. This means the method can only be accessed by code within the same class. In Python, you make a method private by using a double underscore before the method name.
class Bike: def __init__(self, color, current_gear): self.color = color self.gear = current_gear def __increment_gear_by(self, gears): self.gear += gears
If we try to call this method from outside the Bike class, it will throw an attribute error telling us that there is no such method.
If we explicitly include the two underscores in the method call, we get the same error.
But if we create an additional public method “change_gear” within the Bike class, this new method can call the private method. Calling the public method from outside the class will indirectly apply the private method and increment the current gear by 5 from 3 to 8. Running the code, as expected, prints 8 to the console.
Note that Python also has single underscores. While double underscores outright deny external access, single underscores only signal that you should not access the method externally because it is not part of the API. But technically you could access it.
Private Variables in Python
You can also apply the double underscore to variables in a class to make them private.
Let’s add a private variable age to our Bike class. We give it an initial value of 3 so we do not have to change the class constructor signature:
class Bike: def __init__(self, color, current_gear): self.color = color self.gear = current_gear self.__age = 3
If you try to call the age attribute on an instance of the Bike class, you will see an attribute error again. The age attribute can only be called by code inside the Bike class
Python Static Method and Static Variable
We can create a static variable in Python by simply declaring it inside a class but outside of a method.
class Bike: number_of_wheels = 2
This is called a class variable as opposed to an instance variable that is defined inside the constructor.
Contrary to other languages like Java you can access these static class variables not only on the class but also on the instance.
if __name__ == '__main__': bike = Bike("red", 3) print("instance wheels:") # returns 2 print(bike.number_of_wheels) print("class wheels:") # returns 2 print(Bike.number_of_wheels)
Nevertheless, these are two different variables. You could assign a different value 4 to “numberOfWheels” on the bike instance, while the value of the class variable remains 2.
Similarly, you can define static methods using the @staticmethod attribute.
class Bike: @staticmethod def calculate_number_of_wheels(): return 1 + 1
Since static methods are bound to the class rather than to a specific instance of the class, you do not need to pass self to the method call. You can call these static methods directly on the instance, however, they are unable to use or manipulate the instance’s state variables.