You can see Object-Oriented Programming (OOP) somewhat like a more advanced topic in Python where Python does implement it pretty well. Even though Python does not require an OOP approach for making complex programs, it sometimes makes life easier while sometimes it doesn’t. As you will most likely get in touch with some code that has been written in an Object-Oriented approach, it is at least good to have seen the concepts of it.
While Python scripts work pretty well without any implementation of Object-Oriented Programming, it allows you to keep your code even more structured in form of Classes, Attributes and Methods.
In simple terms, a Class is a group of related variables and functions, all bound to a so-called Object. Except, in the scope of OOP, you call the variables Attributes and the functions are called Methods. These objects are meant to behave similarly to real-world objects with individual properties and things that can be done with them. The syntax of creating a class is fairly easy:
class Robot: # remaining code here
As you can see, the class is introduced with the keyword class. Next, there is the name of the class, in this case, it is called Robot. By convention, class names are written with the first letter as a capital letter, the remaining lowercase. If the class name is a compound word such as “Car Battery”, you would write each word starting with a capital letter: CarBattery.
Then, there is another “keyword” called self. In this case, self is not really a keyword of Python as you could call it whatever you like, but the word self has become a convention. In other languages like Java, the fixed keyword would be this where you cannot change the keyword there. In python, you could call it john, roboshack or whatever you like. Sticking to the convention of self isn’t a bad idea though.
The final thing to mention about the class declaration is that it ends with a colon and the following code, that is belonging to the class, is indented, similar to an if-statement or a loop.
As already mentioned, the Attributes are variables that belong to the class object, just like the properties of a real-world object. A robot for example can have a name, a number of wheels, a number of robotic arms, and battery voltage. These Attributes would then be introduced as such:
class Robot: name = "Sony" nr_wheels = 4 nr_robotic_arms = 0 battery_level_volt = 12.3
These Attributes are easily accessible within the function, but also outside of the function. Notice that these attributes are still part of that Object and do not behave in the same way as a global variable. (The scope of variables will be covered later in another part.)
Before you learn how to actually use this Class, let’s have a look at the last part: the Methods.
A Method is basically a function that is tied to an Object of a Class. The Method can easily make use of its Class Attributes, again through the self keyword. One special thing is, that a Method always takes at least one argument: self.
class Robot: say_hi(self): print("Hi!")
The above Method doesn’t make use of its Attributes, but the following for example does:
class Robot: battery_level_volt = 12.3 battery_is_full = True check_battery(self): if self.battery_level_volt > 12.2: print("Battery is still charged!") self.battery_is_full = True else: print("Battery is getting empty!") self.battery_is_full = False
As you can see, the above example is not receiving the actual battery voltage as an argument and yet it can access the battery status through the self keyword. This can help to unclutter functions that would otherwise take quite a lot of input arguments to work with. If you still want to pass some arguments, you can simply add them after the argument self, separated with a comma:
class Robot: battery_level_volt = 12.3 battery_is_full = True battery_charged(self, battery_voltage): if battery_voltage > 12.2: print("Battery is still charged!") return True else: print("Battery is getting empty!") return False
The latter solution is less elegant as it requires additional input and it will give some additional output that might be stored in the battery_is_full Attribute anyways. Additional input parameters make more sense when they contain information from outside of the Class such as a distance to travel or sensor data.
When you create a new variable of the type of a Class, you actually create an Instance of that Class. You can even make several Instances of the same class. This happens as such:
class Robot: name = "Sony" battery_level_volt = 12.3 battery_is_full = True check_battery(self): if self.battery_level_volt > 12.2: print("Battery is still charged!") self.battery_is_full = True else: print("Battery is getting empty!") self.battery_is_full = False my_robot = Robot() my_robot.name = "Turbo" my_robot.battery_level_volt = 13.1 my_robot.check_battery()
The above code will create an instance of a Robot and then save some data into its Attributes and even call a Method of that class. Now, assuming that the class is already given as before, the following example would create two more instances of that Class:
robot1 = Robot() robot1.name = "Nitro" robot1.battery_level_volt = 12.7 robot1.check_battery() robot2 = Robot() robot2.name = "Speedy" robot2.battery_level_volt = 12.0 robot2.check_battery()
As these two robots above are different instances of the same Class, they both have the same Attributes, but they have different values for their Attributes. This keeps things well organized and reduces the risk to override the properties of one object when handling another object. The Method robot1.check_battery() will only check the battery voltage of robot1 and it will not bother about robot2. This is done by using the self.battery_level_volt Attribute inside the Method definition. It will only check the voltage of its own robot.
There is one special type of Method, the so-called Constructor of the class. It is actually called when you type:
my_robot = Robot()
The above line calls the Constructor of the Class. This means that there, Python creates all the Attributes and Methods related to that Object. In addition, this Constructor is able to call a special internal Method with the name init surrounded by two underscores:
class Robot: name = "Sony" battery_level_volt = 12.3 battery_is_full = True __init__(self, name, battery_voltage): self.name = name self.battery_level_volt = battery_voltage self.check_battery() check_battery(self): if self.battery_level_volt > 12.2: print("Battery is still charged!") self.battery_is_full = True else: print("Battery is getting empty!") self.battery_is_full = False my_robot = Robot("Ronny", 12.4)
Now, when the Constructor Robot(“Ronny”, 12.4) is called, it will automatically call the
__init__() Method and it will set the name and the battery voltage to the parameters given to the Constructor. In addition, it will call the check_battery() Method. This can save some work to initialize the object or take some actions that you would otherwise do anyways right after creating a new Instance of that Class.
Inheritance and Polymorphism
Just like in other programming languages, Python classes can be based on other classes. Also, you can overwrite existing methods and overload functions. These topics will not be explained in this guide as they are already more advanced and they would make things even more complicated as they already are at this point. For simple Python programs, and even simple scripts for robotics programming, these concepts are not really necessary at this point in time.
Okay, but why to use Classes?
If you think, this looks more complicated than simply making a bunch of non-OOP variables such as in the following example, you will be proven wrong:
robot1_name = "Nitro" robot1_battery_level_volt = 12.7 battery1_is_charged = robot_check_battery(robot1_battery_level_volt) robot2_name = "Speedy" robot2_battery_level_volt = 12.0 battery2_is_charged = robot_check_battery(robot2_battery_level_volt)
Keeping things organized
At the first sight, the above might look easier to implement. But one big disadvantage simply is, that these are all loose variables. This means if you want to make two instances of the same object, without using a class, each property has its own variable. If you want to copy all the parameters into another variable, you need to specify every single variable. The same holds true if you want to pass all these properties as a parameter into a function, you will need to enter each variable name and while doing so, for each variable you are potentially prone to make a typo.
Without using a class, you will be likely to do the following if you want to copy all the values from one instance to another one:
robot1_name = "Nitro" robot1_battery_level_volt = 12.7 battery1_is_charged = robot_check_battery(robot1_battery_level_volt) robot2_name = robot1_name robot2_battery_level_volt = robot1_battery_level_volt battery2_is_charged = battery1_is_charged
To be fair, you could do it in one single line in Python:
robot2_name, robot2_battery_level_volt, battery2_is_charged = robot1_name, robot1_battery_level_volt, battery1_is_charged
The one-line solution does work, but it doesn’t make it more readable in the case you have many variables. This will be more elegant if you’d just implemented a class containing all the properties and attributes you need:
robot1 = Robot("Nitro", 12.4) robot2 = robot1
Now, your code has copied every class attribute from robot1 into robot2 without a high likelihood of making a typo, no risk to forget one variable and in a very readable way.
Keep things inside a class
Another advantage of implementing a class is that each method has easy access to its class attributes through the self specifier. Also, you can still access the attributes from outside the classes (in Python, you cannot make a variable private or protected like in Java or other languages). As a result, you can make the code easier to read and write as you don’t need to specify every attribute as a parameter for a method call. You just access the data from within the method. In the same way, you can modify the class attributes without the need of having a return value.
So instead of the following code:
robot1_name = "Nitro" robot1_battery_level_volt = 12.7 battery1_is_charged = robot_check_battery(robot1_battery_level_volt)
You simply have the following code in the OOP approach:
robot1 = Robot("Nitro", 12.7) robot1.robot_check_battery()
The Object-Oriented code is shorter, easy to read and modify and it does not contain any unnecessary information. The complexity is more or less hidden inside the method definitions which is fine as you only write the method once, but you can use it as often as you want.
Another important aspect of Object-Oriented Programming is the modularity of the code. You can easily write an external file containing the class definition and then import it to the Python file and make use of the class. This makes the source code more structured and at the same time, you can easily copy the file containing the class definition into other projects. For example, if you use
import rospy, you do exactly that, you import the file containing class definitions for ROS.
Use external code
Even though you may get along pretty well without using classes, you might receive code from a colleague or you find an example on the internet and it has been implemented using OOP. In this case, it is still useful to be familiar with the OOP approach of programming in Python as it is a widely used approach. After all, OOP is there to make things easier. Even though you might want to avoid that initial hurdle to understand how it works, it will probably get to you in any unexpected way.
This is it! Now you know a wide basis of the Python programming language. There are more aspects that have not been covered in detail or not at all. But the parts that have been discussed so far should make it easier for you to get started.
Continue learning about general python programming advice or go back to revisit functions.