It is common secret that Python programming language has a solid claim to being the fastest-growing major programming language witnessing an extraordinary growth in the last five years, as seen by Stack Overflow traffic. Based on data describing the Stack Overflow question views which go to late 2011, the growth of Python relative to five other major programming languages is plotted.

What’s unique about Python?
Python is an object-oriented programming language. Object-oriented programming (OOP) focuses on creating reusable patterns of code. When working on complex programs in particular, object-oriented programming lets you reuse code and write code that is more readable, which in turn makes it more maintainable. Clever use of classes will ultimate benefit a Data Scientist at the stage of productionize its model.
Classes and Objects
Two of the most important concepts in object-oriented programming are:
- Class — A blueprint created by a programmer for an object. This defines a set of attributes that will characterise any object that is instantiated from this class.
- Object — An instance of a class. This is the realised version of the class, where the class is manifested in the program.
These are used to create patterns (in the case of classes) and then make use of the patterns (in the case of objects). Simply put Objects are an encapsulation of variables and functions into a single entity. Objects get their variables and functions from classes. Class is actually the pattern /blueprint for a new object that we can define later.
In the below example, the variable “myobjectx” holds an object of the class “MyClass” that contains the variables and the functions defined within the class called “MyClass”. We manage to access the variable inside of the newly created object “myobjectx” by running the command myobjectx.variable1 and myobjectx.variable2.
class MyClass: variable1 = "blah" variable2 = "hello" def function(self): print("This is a message inside the class.") myobjectx = MyClass() print(myobjectx) print(myobjectx.variable1) print(myobjectx.variable2) #Ouput: <__main__.ObjectCreator object at 0x106146c18> blah hello
At the same time we can access the functions as they are under the class MyClass
and are called methods. Methods are a special kind of function that are defined within a class.
class MyClass: variable = "blah" def function1(self): print("This is a message inside the class.") def function2(self): a = 5 print(a) myobjectx = MyClass() myobjectx.function1() myobjectx.function2() #Output This is a message inside the class. 5
The argument to these functions is the word self
, which is a reference to objects that are made based on this class. To reference instances (or objects) of the class, self
will always be the first parameter, but it need not be the only one.
class MyClass: variable = "blah" def function(self): print("This is a message inside the class.") myobjectx = MyClass() myobjecty = MyClass() myobjecty.variable = "yackity" print(myobjectx.variable) print(myobjecty.variable) # Ouput blah yackity
One of the advantages is that we can create different objects that are of the same class(have the same variables and functions defined). However, each object contains independent copies of the variables defined in the class.
Classes are objects too
As soon as you use the keyword CLASS, Python executes it and creates an OBJECT. In the below example when running the class command creates in memory an object with the name “ObjectCreator”.
It is still an object since:
- you can pass it as a function parameter
- you can add attributes to it
- you can assign it to a variable
class ObjectCreator(object): pass def echo(o): print(o) # you can pass it as a function parameter echo(ObjectCreator) print(hasattr(ObjectCreator, 'new_attribute')) # you can add attributes to a class ObjectCreator.new_attribute = 'foo' print(hasattr(ObjectCreator, 'new_attribute')) print(ObjectCreator.new_attribute) # you can assign a class to a variable ObjectCreatorMirror = ObjectCreator print(ObjectCreatorMirror.new_attribute) # Output <class '__main__.ObjectCreator'> False True foo foo
The Constructor Method
class Shark: def __init__(self): print("This is the constructor method.") def swim(self): print("The shark is swimming.") def be_awesome(self): print("The shark is being awesome.") def main(): sammy = Shark() sammy.swim() sammy.be_awesome() if __name__ == "__main__": main() # Output This is the constructor method. The shark is swimming. The shark is being awesome.
By adding the above __init__
method to the Shark
class in the program above, the program would print automatically:
"This is the constructor method."
This is because the constructor method is automatically initialised (you will never have to call the __init__()
method). You should use this method to carry out any initialising you would like to do with your class objects. For example:
class Shark: def __init__(self, name): self.name = name def swim(self): print(self.name + " is swimming.") def be_awesome(self): print(self.name + " is being awesome.") def main(): sammy = Shark("Sammy") sammy.be_awesome() stevie = Shark("Stevie") stevie.swim() if __name__ == "__main__": main() # Output Sammy is being awesome. Stevie is swimming.
Difference between Class and Instances Attributes
While instance attributes are specific to each object, class attributes are the same for all instances.
class Dog: # Class Attribute species = 'mammal' # Initializer / Instance Attributes def __init__(self, name, age): self.name = name self.age = age # instance method def speak(self, sound): return "{} says {}".format(self.name, sound) # Instantiate the Dog object philo = Dog("Philo", 5) mikey = Dog("Mikey", 6) # Determine the oldest dog def get_biggest_number(*args): return max(args) print("{} is {} years old of {} species while {} is {} years old of {} species.".format( philo.name, philo.age, philo.species, mikey.name, mikey.age, mikey.species)) print("The oldest dog is {} years old.".format( get_biggest_number(philo.age, mikey.age))) print(mikey.speak("Gruff Gruff")) # Output Philo is 5 years old of mammal species while Mikey is 6 years old of mammal species. The oldest dog is 6 years old. Mikey says Gruff Gruff
Attributes can be modified
You can change the value of attributes based on some behavior:
class Email: def __init__(self): self.is_sent = False def send_email(self): self.is_sent = True my_email = Email() print(my_email.is_sent) my_email.send_email() print(my_email.is_sent) # Output False True
Python Object Inheritance
Inheritance is the process by which one class takes on the attributes and methods of another. Newly formed classes are called child classes, and the classes that child classes are derived from are called parent classes.
It’s important to note that child classes override or extend the functionality of parent classes. In other words, child classes inherit all of the parent’s attributes and behaviours but can also specify different behaviour to follow.
# Parent class class Dog: # Class attribute species = 'mammal' # Initializer / Instance attributes def __init__(self, name, age): self.name = name self.age = age # instance method def description(self): return "{} is {} years old".format(self.name, self.age) # instance method def speak(self, sound): return "{} says {}".format(self.name, sound) # Child class (inherits from Dog() class) class RussellTerrier(Dog): def run(self, speed): return "{} runs {}".format(self.name, speed) # Child class (inherits from Dog() class) class Bulldog(Dog): def run(self, speed): return "{} runs {}".format(self.name, speed) # Child classes inherit attributes and # behaviors from the parent class jim = Bulldog("Jim", 12) print(jim.description()) # Child classes have specific attributes # and behaviors as well print(jim.run("slowly")) # Is jim an instance of Dog()? print(isinstance(jim, Dog)) # Is julie an instance of Dog()? julie = Dog("Julie", 100) print(isinstance(julie, Dog)) # Is johnny walker an instance of Bulldog() johnnywalker = RussellTerrier("Johnny Walker", 4) print(isinstance(johnnywalker, Bulldog)) # Is julie and instance of jim? print(isinstance(julie, jim)) # Ouptut Jim is 12 years old Jim runs slowly True True False --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-17-c976ae4d1a2c> in <module> 52 53 # Is julie and instance of jim? ---> 54 print(isinstance(julie, jim)) TypeError: isinstance() arg 2 must be a type or tuple of types
It does make sense as jim
and julie
are instances of the Dog()
class, while johnnywalker
is not an instance of the Bulldog()
class. Then as a sanity check, we tested if julie
is an instance of jim
, which is impossible since jim
is an instance
of a class rather than a class itself—hence the reason for the TypeError
.
Overriding the Functionality of a Parent Class
As a class can override its class attribute in the same way a child class can also override attributes and behaviours from the parent class. For example:
class Dog: species = 'mammal' class SomeBreed(Dog): pass class SomeOtherBreed(Dog): species = 'reptile' frank = SomeBreed() print(frank.species) beans = SomeOtherBreed() print(beans.species) # Output mammal reptile
The SomeBreed()
class inherits the species
from the parent class, while the SomeOtherBreed()
class overrides the species
, setting it to reptile
.
Advanced: Why/How to use ABCMeta and @abstractmethod
To understand how this works and why we should use AGCMeta, let’s take a look at an example. Let’s say we have a Base class “LinearModel” with two methods (prepare_data & fit) that must be implemented by all derived classes.
class LinearModel(): def prepare_data(self): raise NotImplementedError() def fit(self): raise NotImplementedError() class LinearRegression(LinearModel): pass c = LinearRegression() c.fit() # Output --------------------------------------------------------------------------- NotImplementedError Traceback (most recent call last) <ipython-input-36-8b647c03f898> in <module> 1 c = LinearRegression() ----> 2 c.fit() <ipython-input-33-e12db09990e4> in fit(self) 6 7 def fit(self): ----> 8 raise NotImplementedError() NotImplementedError:
When we instantiate an object c
and call any of it’s two methods, we’ll get an error (as expected) with the fit()
method.
However, this still allows us to instantiate an object of the LinearModel()
class without getting an error. In fact we don’t get an error until we look for the fit()
.
This is avoided by using the Abstract Base Class (ABC) module. Let’s see how this works with the same example:
from abc import ABCMeta, abstractmethod class LinearModel(metaclass = ABCMeta): @abstractmethod def prepare_data(self): raise NotImplementedError() @abstractmethod def fit(self): raise NotImplementedError() class LinearRegression(LinearModel): pass c = LinearRegression() # Output --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-39-dfea013b4884> in <module> ----> 1 c = LinearRegression() TypeError: Can't instantiate abstract class LinearRegression with abstract methods fit, prepare_data
This time when we try to instantiate an object from the incomplete class, we immediately get a TypeError! Even if the fit function has been defined properly in the parent class.
from abc import ABCMeta, abstractmethod class LinearModel(metaclass = ABCMeta): @abstractmethod def fit(self): print('Hello') class LinearRegression(LinearModel): pass c = LinearRegression() # Output --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-40-6e09917448d0> in <module> 9 pass 10 ---> 11 c = LinearRegression() 12 13 # Output TypeError: Can't instantiate abstract class LinearRegression with abstract methods fit
This force us to complete the class LinearRegression to avoid any Errors:
from abc import ABCMeta, abstractmethod class LinearModel(metaclass = ABCMeta): @abstractmethod def fit(self): print('Hello') class LinearRegression(LinearModel): def fit(self): print('Bye') c = LinearRegression() c.fit() # Output Bye
This time when you instantiate an object it works!
Example when this can be useful
If you make a GTA-like game where you can drive different vehicles, you create an abstract class “Vehicle” with an abstract method “drive”. You then have different types of vehicles, each with its own class: “Car”, “Bike”, “Train”, … . Each of these classes will have a different implementation of “drive”. The train will be bound to tracks, the bike to lower speeds, the car to other rules, …
This is much more efficient than having your drive method implemented in “Vehicle”. You don’t need to add all the “if class == Car/Bike/…” bullshit, which is extremely ugly. Also no vehicle will exist as just “Vehicle”, they will always belong to a child class, therefore the “Vehicle” class remains abstract, empty.
Stay tuned for my next article on Python metaclasses.
Conclusion
Object-oriented programming is an important concept to understand because it makes code recycling more straightforward, as objects created for one program can be used in another. Object-oriented programs also make for better program design since complex programs are difficult to write and require careful planning, and this in turn makes it less work to maintain the program over time.

Thanks for reading and I am looking forward to hear your questions 🙂
Stay tuned and Happy Coding.
P.S If you want to learn more of the world of machine learning/coding you can also follow me on Instagram, email me directly or find me on linkedin. I’d love to hear from you. Resources:
https://docs.python.org/3/library/abc.html
https://www.python.org/dev/peps/pep-3115/
https://riptutorial.com/python/example/23083/why-how-to-use-abcmeta-and–abstractmethod
Source: towardsdatascience