Python Basics
Declare Your First Python Class — Understand 3 Basic Components
Better organize your code using custom classes
When our project’s scope grows, we’ll find it gradually tedious to manage data using built-in data types. For example, let’s say we’re building an employee management tool, and thus we’ll need to deal with data related to individual employees. Using the built-in dictionary data type, we’ll have to use the following data structure (i.e., dictionary) to refer to a particular employee.
>>> employee = {"name": "John Smith", "employee_id": 10997, "gender": "M"}
Say that we want to access this employee’s information, we’ll have to use the square brackets to retrieve particular values.
>>> employee["name"]
'John Smith'
>>> employee["gender"]
'M'
>>> employee["employe_id"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'employe_id'
It’s all good if we’re careful enough to spell the keys correctly. However, if we misspell the key, the corresponding value can’t be retrieved. Moreover, our program can even crash. Although many advanced Python editors, such as Visual Studio Code and PyCharm, have the intelligent code feature, they don’t always offer hints about the keys for dictionaries.
What’s the better alternative?
The custom class comes to the rescue! Declaring a custom class is very straightforward in Python, which you’ll find shortly. When we declare a custom class, we’re basically drawing a blueprint on what data (e.g., attributes) these classes can store and what functionalities (e.g., methods) they can provide. Importantly, these attributes and methods are accessible using the more slick dot notation and they’re supported by intelligent code hinting by most modern integrated development environments — more commonly known as IDE.
In this article, let’s see how we can take advantage of creating a custom class to manage the data. Specifically, we’ll focus on three basic components that most custom classes will implement.
1. Instantiation
The very first thing that we need to understand is that the purpose of creating custom classes is that we can create instance objects of these classes. Most of the time, it’s the instance objects (or simply referred to as instances) that actually store the data and perform desired operations.
The process of creating an instance is known as instantiation. In other words, we instantiate an instance object of the class. In most cases, we use the class constructor to create an instance. Too much jargon here? No problem! Let’s see some examples to understand all of them.
>>> # Define a class
>>> class Employee:
... def __init__(self):
... print("An employee instance is created")
...
>>> # Create an instance
>>> employee = Employee()
An employee instance is created
In the above code, we declared a custom class called Employee
. As you can see, we defined a function called __init__
, which means initialization. In Python, a function with a double-underscore prefix and a double-underscore suffix is known as a magic function. When we say magic, it means that it has some special meanings. In this case, the __init__
is reserved as the constructor method in Python. We’ll talk about what the self in the function means in the next section.
What’s a constructor method? The construction just means the creation of an instance object. In the above code, we simply use a pair of parentheses following the class name to create the instance. If you want to learn more about the instantiation process about Python classes, you can refer to my previous article on this topic.
To verify the variable employee has the desired type, we can use some built-in introspection functions, including type()
and isinstance()
, as shown below.
>>> # Check the variable employee's type
>>> type(employee)
<class '__main__.Employee'>
>>> isinstance(employee, Employee)
True
2. Attributes
In the above section, we declared a class, but our class couldn’t really do anything. One problem is that the instances can’t store any data. For example, in the beginning, the dictionary object stores some basic information for the employee.
The purpose of creating the custom class Employee
is to make our data management job easier, isn’t it? It brings us to the introduction of attributes for the class. Let’s see an updated version of the Employee
class.
>>> # Update the class's init method
>>> class Employee:
... def __init__(self, name, employee_id, gender):
... self.name = name
... self.employee_id = employee_id
... self.gender = gender
...
As shown in the above code, besides the self
argument, the updated initialization method takes name
, employee_id
, and gender
arguments. What does self mean here? In essence, it refers to the newly created instance object, such that we can set its corresponding attributes (e.g., self.name
). To learn more about the self argument, please refer to my previous article.
With the updated initialization method, we’ll call the constructor method by providing needed parameters, like blow. Again, we don’t need to specify the self
argument, because Python implicitly sets the newly created instance as the self
argument (see the above article for more information).
>>> # Create an instance
>>> employee = Employee("John Smith", 10997, "M")
As I mentioned above, one reason that we want to use custom classes is the convenience of accessing attributes using the dot notation. In addition, we can change them if we want to. Let’s see some examples below.
>>> # Access the instance's attributes
>>> employee.employee_id
10997
>>> employee.gender
'M'
>>> employee.name = "John David Smith"
>>> employee.name
'John David Smith'
One thing to note is that we refer to these above-defined attributes as instance attributes, which means that these attributes belong to a particular instance. A different instance object can have different attributes, as shown below.
>>> # Create another instance
>>> another_employee = Employee("Jennifer Davis", 11308, "F")
>>> another_employee.name
'Jennifer Davis'
>>> another_employee.employee_id
11308
>>> another_employee.gender
'F'
In addition to instance attributes, we can also declare class attributes. Unlike instance attributes that are instance-specific, class attributes are “owned” by the class (remember class is also an object in Python such that it can have attribute). Importantly, instances can access these class attributes directly, which is usually illegal in other programming languages (e.g., Swift). In these languages, only the class itself can use the class attributes directly.
>>> # Update the class with a class attribute
>>> class Employee:
... company = "Python Foundation"
... # same __init__ function as before
...
>>> # Create new instances
>>> employee0 = Employee("Aaron", 20313, "M")
>>> employee1 = Employee("Jimmy", 20443, "M")
>>>
>>> # Access class attributes
>>> employee0.company
'Python Foundation'
>>> employee1.company
'Python Foundation'
>>> Employee.company
'Python Foundation'
As shown in the code, the updated class has a class attribute called company
. Notice that the new instances and the class all have access to the class attribute. Moreover, we can change the class attribute, as shown below.
>>> # Update the class attribute
>>> Employee.company = "Python"
>>> Employee.company
'Python'
>>> employee0.company
'Python'
3. Methods
Now, our class is able to store data and we can retrieve and change their attributes as needed, what else do we need?
Some functions. That’s right. Our custom class isn’t able to perform any operations, and let’s fix that.
>>> # Update the class with some methods
>>> class Employee:
... # Keep the init method the same
...
... # Declare an instance method
... def request_vacation(self, start_date, days):
... print(f"Request {days} days off starting on {start_date}")
...
... # Declare a static method
... @staticmethod
... def send_notice(content):
... print(f"Every employee is notified: {content}")
...
In the code above, for demonstration purposes, we created two simple methods, namely request_vacation
and send_notice
. As noted by the comments, the first function is an instance method, which means that the caller is an instance object of the class. Notice the use of self argument in the function declaration? That’s the indication of an instance method in Python. It’s a convention to use self to refer to an instance object of a class.
The second function is a static method, which means that the caller is the class itself (although technically an instance can call this function too, pretty much like a class attribute as discussed above). The static method is sometimes also called a class method in other programming languages, but they’re not exactly the same in Python, the discussion of which is beyond the current article’s scope. By the way, @staticmethod is known as a decorator function that is used to declare a static method in a class. You can learn more about decorators in my previous article.
Let’s see how we can call these methods.
>>> # Create an instance
>>> employee = Employee("David Johnson", 20340, "M")
>>>
>>> # Call the instance method
>>> employee.request_vacation("2020-06-02", 10)
Request 10 days off starting on 2020-06-02
>>>
>>> # Call the static method
>>> Employee.send_notice("Hello")
Every employee is notified: Hello
>>> employee.send_notice("Hello")
Every employee is notified: Hello
As explained above, we created an instance, and the instance can call both the instance and static method. The class itself can call the static method, which I recommend should be the preferred way to use a static method, because it doesn’t need to create an instance and minimize confusion (i.e., a static method doesn’t rely on any instances).
What will happen if we try to use the class to call the instance method?
>>> # Call the instance method with the class
>>> Employee.request_vacation("2020-06-02", 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: request_vacation() missing 1 required positional argument: 'days'
Apparently, the function call didn’t succeed, because Python will look for the instance object and assign it to the self
argument. So, please don’t make this mistake.
Conclusions
In this article, we reviewed how to declare a custom class in Python. Specifically, we covered the most basic elements that we usually implement in a custom class declaration. Here’s a quick recap.
- We use the
__init__
function to create the default instance constructor for the class. We set the most important instance attributes to the newly created instance during the initialization process. - We can declare attributes both for the instance and class. Instance attributes are specific to individual instances, while class attributes belong to the class, and class instances have access to the class attributes.
- We can declare instance and static methods in our custom class. The instance method declaration requires the use of the self as its first argument, while the static method declaration uses the @staticmethod decorator conventionally.