Learn everything about Analytics

Home » The Nature of Object-Oriented Programming in Python

The Nature of Object-Oriented Programming in Python

This article was published as a part of the Data Science Blogathon.

Introduction

Classes are one of the most thought-out concepts in python, this is because Python is an object-oriented programming language for the most part; everything in python is an object. Classes are object constructors or templates which define how an object should look/behave. Consisting of both data and function (called methods when contained in classes) python classes have an enormous feature list and are possibly the most comprehensive portion of the entire programming language. Python classes leverage polymorphism and inheritance.

 

Encapsulation

Interestingly there are a few things that python as a language did not see fit to overcomplicate, one of those things is encapsulation. In python, encapsulation is merely achieved via naming conventions. The expectation is that a python programmer will be familiar enough with the naming convention to know and observe this rule.

To indicate that a class variable or method is meant to be used only internally, a single underscore(_) is appended before the name. This same principle can be applied to modules as well module-level functions, one good example is sys._getframe(). Internal variables, methods, etc are typically meant for “internal” use, external use is often discouraged, this is because changes to them do not reflect in library documentations in any way, extreme caution should always be taken because the use of them may result in fragile code.

class Cells:
def __init__(self):
self._internal = 0 #internal

def public_method(self):
”’
A public method
”’
pass

def _internal_method(self):

pass

Internal objects(instance attributes, methods, etc) are often used as a means to an end, to achieve a particular objective within a public method.

Another pattern python uses is the leading double underscore(__) to privatize class variables or methods in classes. Earlier in this post, I mentioned how python classes leverage inheritance and polymorphism for their functionality.

Polymorphism is the ability of a child class to override a particle attribute from the parent class. This is often the necessary when a parent method doesn’t quite fit the needs of the child class. It is achieved by overriding the method in the child class through redefinition.

However, there is a way to ensure python does not do that. This is accomplished by using private objects(methods, class variables, etc). Private and internal methods both seek out the same objective of achieving something within a public method to make that method work as should without revealing too much to end-users, it also ensures that your function does not grow too large, hence becoming too difficult to manage.

class Cell:

def __init__(self):

self.__id = 0

def __private_method(self):

pass

def public_method(self):

self.__private_method()

Unlike the previous example with the single leading underscore(_), using double underscores(__) actually causes python to do something called name mangling.

class AnimalCell(Cell):

def __init__(self):

super().__init__()

self.__id = 1 #does not override self.__id from the Cell class

def __private_method(self):

”’

does not override the __private_method in the Cell class

”’

pass

Name mangling ensures that the names do not collide, by altering them enough to prevent conflict or overriding.

Here, the private names __private and __private_method get renamed to _AnimalCell__private and _AnimalCell__private_method, which are different than the mangled names in the base
class Cell. This mangling also applies to the Cell class. In essence, python adds a single leading underscore and the name of the class before the name of the attribute, this ensures individuality/uniqueness.

One other naming convention to note is the use of a trailing underscore(comes after the name of the object). This convention is used to avoid collision with names of reserved keywords, an example is using in_ instead of in because “in” is a reserved keyword in python.

 

Objects and Metaclasses

The concept of metaprogramming is one of the most widely used paradigms in all computing, with each language implementing it how they see fit, the topic though simple enough is quite broad. In a nutshell, metaprogramming is about creating functions and classes whose main goal
is to manipulate code. Check out metaprogramming in python to learn more about it. One way to achieve this in python is with the use of metaclasses.

Recall that in writing classes, the use of dunder methods(class methods with two trailing and two leading underscores) is used. One dunder method that most people would have come across is the __init__, method used in the creation of class instances.

class Analytics:

def __init__(self, site):

self.site = site

Analytics(“www.analyticsvidhya.com”)

For each new instance of the Analytics, the class created the self.site attribute can be changed, but there is a way to go further. Before __init__ there is one dunder method which is called, this is the __new__ method and it is called before any other dunder method. This __new__ method is what is responsible for the creation of a class, while __init__ is responsible for the instantiation of said class.

 

Recall in the first paragraph I made two statements

  1. Everything in python is an object
  2. Classes are constructors or templates that define how an object looks/behaves

What this means is that every single thing in python is created by some form of a constructor or another. Python leverages on inheritance and constructors to achieve a lot of things object-wise

For example, ‘str’ is used as the template for creating strings in python, this is why

isinstance(‘maestro.inc’, str)

resolves to true.

The same principle applies for integers with int and the same also applies for classes with the type(notice it is in lower case).

Classes like everything else in Python are objects, this means that they are also created based on some constructor or template, these templates/constructors are what we refer to as metaclasses. Python essentially creates a new class by calling the metaclass.

Now, this might raise a new question:

“What are str, int, float and the likes constructed from?”

Well to put it as simple as possible, they are essential metaclasses of themselves.

 

So, how do metaclasses work???

The answer to this is pretty straightforward compared to everything discussed so far

  • intercept a class creation
  • modify the class
  • return the modified class

class MyMeta(type):
def __new__(cls, clsname, bases, clsdict, **kwargs):
for name, value in clsdict.items():
print(name, value)
return type.__new__(cls, clsname, bases, d)

class myClass(meta=MyMeta):

def __init__(self):

pass

So let’s do a quick breakdown of the metaclass code above.

  1. The metaclasses you create inherit from the type object and this gives them the ability to be a level lower than default python classes.
  2. Personally, I prefer to use the __new__ method, but some people feel more comfortable using __init__ which they are more familiar with. Regardless of which of the two you chose, the next step remains the same: defining them with 4 positional(compulsory) arguments, optional parameters can be made available too. cls is the metaclass itself, clsname is the name of the future class, bases are the parents of the future class and clsdict is a dictionary that contains information about the attributes of the future classes.
  3. Looping through the dict attributes of future classes, we can now check or alter them as we see fit
  4. Finally, we return the modified class

Setting the meta of a class to your custom metaclass will ensure that the class is created as specified in the metaclass. Assuming you decided to make use of optional or keyword parameters while creating your metaclass, those can be supplied through the class as well

class myClass(meta=MyMeta, kwarg1=value1, kwarg2=value2):

def __init__(self):

pass

Then the keyword or optional arguments get passed to the metaclass to do with them as intended.

Defining classes programmatically

def __init__(self, name, shares, price):
self.name = name

self.shares = shares
self.price = price

def cost(self):
return self.shares * self.price

cls_dict = {
‘__init__’ : __init__,
‘cost’ : cost,
}

# Make a class
import types
Stock = types.new_class(‘Stock’, (), {}, lambda ns: ns.update(cls_dict))
Stock.__module__ = __name__

The above code was gotten from O’Reilly’s python cookbook

Creating class programmatic-ally is rather simple once you understand what constitutes a class. Realizing that all classes come with their own dictionary is one of the keys to helping you define classes easily. All a programmer really needs to do is define the attributes the class requires and have them added to the class via the use of types: the object responsible for the creation of classes as I stated earlier. This results in a normal class object that works properly as every other class would. The tuple in the types.new_class function would hold classes inherited from and the empty dictionary would hold various keyword arguments of the class definition like metaclass, etc.

s = Stock(‘ACME’, 50, 91.1)

s.cost() #returns 4555.0

Metaclasses are not the most common of solutions, in fact, 98% of the time, the use of class decorators will do the trick, however, it is a good item to have in your toolkit for the remaining 2% of the time when class decorators just won’t cut it.

My name is Oghenemarho Onothoja, a software engineer skilled at full-stack development and quality assurance testing(performance testing). Follow me on Twitter @marho219, connect with me on LinkedIn, and check out my posts of medium where I dive more into metaprogramming in python as well as other topics. Follow me on GitHub. If you want to reach me, send me a DM on Twitter or send me an email at [email protected]

You can also read this article on our Mobile APP Get it on Google Play