When and How to use Classes?

Discussing classes in content of Pytubedata

When I started writing the initial version of Pytubedata, the primary goal in my mind was to structure the code in a way that would be easy to scale (extending functionalities) and maintain in later stages. How did I do it? Classes.

I, for a long time, didn't understand the real purpose of classes. However much I read about them, they were hard to grasp until I started using them. In this article, I will share my understanding of Classes with examples from Pytubedata.

Define the requirements

Before writing any piece of code, we should first explicitly define its functionalities and its limitations. What it will do and what it will not do, both are important. This was what I wanted Pytubedata to do-

Make an API call to public endpoints of YouTube Data API and return the response.

To make those requests it needs 4 things- base URL, endpoint, API key, and parameters.

Class and its Members

A class, as I would define it, is a blueprint that primarily holds data and functions, also called members of the class, together.

The base URL and API key are used in all requests, so it makes sense to keep them together. Creating the first class api that would hold this data together.

class api:
    def __init__(self, key):
        self.base_url = "https://youtube.com/api/v3"
        self.key = key

The __init__ function here is a constructor. A class is a blueprint, and a blueprint is a general model to create something. You can create multiple entities using that blueprint. A constructor's job is to assign values to variables whenever a new entity is created using that class. (more on entities below)

Now left the endpoint and parameters. An API can have many endpoints and each endpoint will have different parameters. So let's create a separate class for each endpoint and store the endpoint and the parameters together.

def Channel:
    def __init__(self):
        self.endpoint = "/channels"

    def get_channel(self, id, max_results):
        params = {
            "channelId": id,
            "part": snippet,
            "maxResults": max_results
        }

        return params

Since some parameters take the user's input so we can create a function to assign the value to those parameters.

Put it all together

Now we need to define a function that will make the API calls. Since the api class holds the key and base_url for the API call, we will define a function in the same class. (Note: now the class is holding data and function that use that data together)

import requests

class api:
    def __init__(self, key):
        # data members
        self.base_url = "https://youtube.com/api/v3"
        self.key = key

    # method to make API call
    def request(self, channel_id, max_results):
        requests.request(
            method="GET",
            url=self.base_url
            params= # how do I put params here?
        )

Having all the data structured now we are ready to make an API call. But wait, how are we gonna put all this data together? Remember entities from the constructor discussion?

Those entities are called objects. Objects are essentially copies of a class and we can make as many as you want of them. So when we want to make an API call to the channel endpoint we can just create the object of Channel class.

    def request(self, channel_id, max_results):
        c = Channel(channel_id) # creating object of Channel Class
        requests.request(
            method="GET",
            url=self.base_url
            params=c.get_channel(
                        id=channel_id, 
                        max_results=max_results
                    )
            )

Now the main code

key = "123"
client = api(key=key)

client.request(channel_id="123", max_results=10)

There, we successfully made an API call that returns data about a channel given its id.

Encapsulation

Encapsulation is what makes a class special.

It extends the holding functionality of the class with the ability to restrict access of data members and functions to the outside world using access modifiers. A motivation for this concept; someone using this code can modify the value of key or base_url by accessing them using objects which can break the code.

key = "123"
client = api(key=key)

client.key = "867" # value of key changed

There exist 3 access modifiers:

  • Public

    • Public is the default access modifier and, as the name suggests, anyone can access the public members of a class using the object
  • Protected

    • Protected members can be accessed only within the class and its child classes (You will learn about these in Inheritance)
  • Private

    • Private members cannot be accessed anywhere outside the class

To prevent malicious coders from breaking our code, in our case manipulating variables like endpoint, base_url, and api_key we can make these variables Private.

In Python, there doesn't exist the concept of access modifiers. But there is a hack to mimic the same behavior using underscore (_).

You can add two underscores before a variable, which will tell Python that those members are private and not to be given access outside the class.

import requests

class api:
    def __init__(self, key):
        # data members
        self.__base_url = "https://youtube.com/api/v3"
        self.__key = key

    # method to make API call
    def request(self, channel_id, max_results):
        requests.request(
            method="GET",
            url=self.base_url
            params= # how do I put params here?
        )
key = "123"
client = api(key=key)

client.__key = "abc"

Now this will give an error since __key is a private variable and cannot be accessed outside the class.

One shortcoming of using private variables is that now the user won't even be able to peek at the values of the private variables, which can be an issue sometimes. For eg, here one might wanna look at the value of __key and if it is assigned the right value. We can just define a Public function, that will return the value of __key , within the class.

def get_key(self):
    return self.__key

Now that you have understood encapsulation, I would like to redefine the class definition-

A class is a blueprint that primarily encapsulates data and functions, also called members of the class, together.

Let's summarize the concepts we have covered in this article-

  • Classes

  • Constructors

  • Objects

  • Encapsulation

This is the most basic functionality of classes. From here, a lot more has been added to classes but that's for another blog. Until then, check out getter and setter in Python.