Now we arrived at object-oriented programming. This has (had) its hype where every language was designed around objects and Python developer Guido van Rossum thought “Why not?” and added classes to support object-oriented development. Some python evangelists think this was a bad decision, some think it was a good approach…
Object-orientation is a big topic. One could write a book about it but I will stick to be within the small boundaries of an article. Or maximum two articles about OO in general and OO in Python.
OO is around the late sixties but only gained space among developers in the early nineties. It has four major principles we will learn about:
- Data abstraction
If I want to an analogy of real-life I would say OO is just like a restaurant. You can have two types of them: one where the food is in counters accessible and you can self-serve yourself with food; the other is where you go in and ask for food and it is prepared and brought to you by professional service.
The first version with self-service is something imperative languages use (like C or simple Python scripts for example) where you have everything accessible by everyone and they can use what they will. In this case sometimes dishes can stay on the table because bringing them back is a job of the customer.
The second version is OO. There you encapsulate the functionality and have access only to those parts which are publicly available. If you developed already with Java or C++ you may know the idea of public, protected and private access. In this case getting the food and bringing back the dishes happens through the staff. They know what to get from where and where to put things back and the end-user does not need to know everything.
If we look back to object-orientation then we can say that a class is a definition of an object and one object is the instance of a specified class.
A class defines which attributes and function the future object has and if the language uses access restrictions you can tell in the class definitions which parts are accessible for the public, for extensions of the class or only for internal use.
Now it is time to dig deeper into the four major principles of OOP.
Encapsulation means the packaging of data and functions into a single component. In our case into a class however other programming languages support other alternatives. And the functions of the class operate on the data stored in the class’ fields.
In some programming languages encapsulation is used to hide information, or to be more precise: restrict access to data and functions. These languages include C++ and Java for example where you can use private, protected and public to restrict access to fields and methods. However in Python there is no such restriction level. You can access every field and function of a class.
However there is a convention which is not written down but every Python developer knows and should know: members of a class (fields and functions) which name starts with double underscore (__) should be treated as private and should not be called or accessed.
Data abstraction enforces a clear separation between abstract properties of a type and the implementation details. The abstract properties are those which are visible to the client using this data type (in our case it is the class, in other programming languages it is an interface definition) and the implementation is hidden from the client and is held private.
And because the implementation is kept private it can be changed over the time (to make the code faster for example) and the client does not notice this change because the abstraction stays the same.
Well, in Python there is nothing like an interface in other OO languages. You only have your class and this class has its fields and functions. Of course you can have a “public” function as the interface to outside code and one or more “private” functions which implement the logic of that “public” function. But this is not a real abstraction.
However Python knows a solution for read-only fields which are calculated on-the-fly with so called “getter” methods. Naturally the same goes for read-write fields where Python gives us the possibility to use “setter” methods too. We will see later examples of both.
Inheritance is a key feature of OOP where one class is based on the template / implementation of another class (inheriting from this class). This is an essential method of code-reuse where you can encapsulate common functions and information between subclasses into one base class.
There are different types of inheritance models but the two most common are single inheritance and multiple inheritance. Python uses multiple inheritance which means that one class can extend as many classes as it want.
Inheritance is often confused with object composition. Sometimes new developers try to solve every problem with inheritance even if it should be object composition. Object composition means that you have a property which is the instance of another class but your class is not extending it. If you do not know which one you need just keep following simple solution in mind:
Inheritance is an is-a relationship meaning a car is a vehicle. Object composition is a has-a relationship meaning a car has a wheel (or at least a car has wheels).
Polymorphism is in OOP the provision of a single interface to multiple types. In Python this means that you expect the superclass as parameter (for example you do a isinstance()-check) and call common methods of this superclass on the object. Now with polymorphism the real implementation of the method will be executed which is in the subclass used.
>>> class Animal:
... def sound(self):
... raise NotImplementedError
>>> class Dog:
... def sound(self):
>>> class Dog(Animal):
... def sound(self):
>>> class Cat(Animal):
... def sound(self):
>>> def animal_sound(animal):
... if isinstance(animal, Animal):
... print("Not an animal, do not know how to make it sound")
>>> cat = Cat()
>>> dog = Dog()
As you can see in the example above the animal_sound function verifies that the argument is an Animal and then it calls the sound method of that particular animal.
When to use OO?
Naturally OO is not the snake oil for everything. Therefore you should consider using OOP when developing. In this section I will take a deeper look at when to apply the principles and techniques of this chapter.
Deciding when to use object-oriented programming is not easy. We have to keep in mind that objects have data and behavior which makes things complicated. That’s why many Python developers use simple data-structures (lists, sets, dictionaries) and simple functions unless an extra layer abstraction is really needed (and so am I too).
Now if we see that we are calling functions with the same data-set we can think about encapsulating this data into a class and add the functions as class functions to represent the behavior.
A simple example would be something out of geometry. There you use a 2-tuple (a pair) for storing points. A list of points represents a shape (a polygon). So you start with defining a list containing some pairs representing the points:
triangle = [(2,3), (5,7), (0,0)]
Now if you want to calculate the perimeter of this triangle you would write a function which would look like this:
perimeter = 0
points_extended = points + [points]
for i in range(len(points)):
perimeter += math.sqrt((points_extended[i] - points_extended[i+1])**2 + (points_extended[i] - points_extended[i+1])**2)
After arriving at this point you could feel that there could be an object encapsulating all the points of the triangle (the data) and the perimeter function (behavior). If you think a bit more you would encapsulate the x and y coordinates of the triangle’s points into another object and add the distance calculation of two points to this object.
And there are classes in Python which you can use for this encapsulation.
In the next article I will dig into Python’s way of defining and using object (classes) and I have to tell you beforehand there is some more ways than you can imagine.