Check out this video for an overview of the article. Then, read the article and solve exercises to strengthen your understanding.
1 Introduction
In object-oriented programming (OOP), you can represent real-world things as objects with data and functions. This approach makes code more organized and reusable. In this style of programming, objects are created from classes, which act as blueprints for objects.
“Python” language is an object-oriented programming language, and classes are a fundamental part of object-oriented programming. Classes allow you to create new types of objects with their own attributes and methods. In this document, we will explore the concept of classes in “Python” language, their syntax, and usage. A class is a complicated concept for the beginners, so I will cover only the basics of it.
2 What is a Class?
In “Python” language, a class is a blueprint or a template for creating objects. It defines a data structure that describes the properties and behaviors of objects that will be created based on that class.
2.1 A Class and an Object
Through this article, I will often use the term “class” and “object”. If this is your first time to learn a class, the two words may be confusing. As I mentioned, a class is a blueprint for creating objects (instances), while an object is a specific instance of a class. However, what does that mean? A good example will help you grasp the concept.
Imagine that you are a teacher in a school. This is a new school year, and you will have some students in your class. You need to create student-information sheets to understand the details of them and make their school lives safe and fun. For this purpose, you created the following sheet:
3 Student Information Sheet
Full Name
Address
Parent / Guardian Information
What is your child interested in?
This empty sheet works as a blueprint or template. You will send this sheet to the parents or guardians of your students, and they will fill in the sheet. The filled-in sheets are the real container of your students’ information. Now, you can understand that the template and the filled-in sheets correspond to the class and objects, respectively.
TIPS: Metaphor for Understanding a Class and an Object
- Empty student-information sheet: Class
- Filled-in sheet: Object
In a nutshell, you needed a container for your students’ information. You modeled the container as the student-information sheet (class). Although this empty sheet is a template, the parents or guardians filled in the sheet and sent them back to you with the real information. These filled-in sheets work as the instance of the template with the real data. These are the objects. As you can see in this example, you will have multiple objects from a single class.
Also, it should be noted that objects are called instance in “Python” programming. So you will create an instance based on a class (@note-cla-obj-ins).
NOTE
Strictly speaking, a class is called a “class object” in “Python” language. In this mean, a class is probably an object in “Python” language. As far as I understand, however, in Object-Oriented programming, it is common practice to distinguish between a class and an object (instance). Therefore, I will treat them as separate concepts.
Now, it’s time to check how you define the class and how they work in “Python” programming. We will take a circle as an example and create a class.
4 Syntax for Defining a Class
First, we will check how to define a class. In “Python” language, the syntax for defining a class is as follows:
TIPS: Defining a Class
class ClassName: # Class body # Define attributes and methods here
The class
keyword is used to define a new class, followed by the class name. The body of the class contains attributes (data) and methods (functions) that define the behavior of the class.
Using the class
keyword, I create a class for a circle:
class Circle:
pass
This class is empty, so I will add some attributes and methods.
5 Attributes and Methods
You have learned variables and functions in this crash course. As you know, variables are containers of data, and functions are set of instructions that you can call whenever you want.
A class or an object can have the variables and functions inside them. An attribute is a variable defined in a class, while a method is a function defined in a class. Let’s check the meaning of those terms in detail.
5.1 Attributes
An attribute is a variable defined in a class. If an attribute is shared across all instances of the class, it is called a class attribute. On the other hand, when the value of an attribute is not shared through the instances, it is called an instance attribute:
class Circle:
# Class attribute
object_type = "plane"
# Constructor method that initializes a Circle object with the coordinates of the center and the radius
def __init__(self, center, radius):
# Instance attributes
self.center = center
self.radius = radius
In the above code, I added three attributes, one is a class attribute object_type
and two of them are instance attributes, center
and radius
. A circle is a plane object, which is common across all circles. So I created the object_type
as a class attribute. On the other hand, each circle has its own center and radius, and I defined them as the instance attributes. Note that the instance attributes have self
. This is a convention used to represent the instance of the class itself. By adding self
in front of the attribute name, we can allow each instance to have its own value for the attributes (Tip 1).
Tip 1: TIPS:
self
The
self
represents the instance of the class itself.
NOTE
The
__init__()
method is a special method in “Python” classes that is called when a new instance of the class is created. It is used to initialize the object’s attributes or perform any setup that is necessary. I will explain it later in this article (Section 5.2).
In the next sections, we will check the differences between the class attribute and the instance attribute in detail.
5.1.1 Class Attributes
Class attributes are defined directly inside the class but outside of any methods. They are shared by all instances of the class:
# Create an instance of the Circle class with center coordinates (0, 0) and the radius 1.
circle_1 = Circle([0, 0], 1)
# Create an instance of the Circle class with center coordinates (1, 1) and the radius 2.
circle_2 = Circle([1, 1], 2)
# Print the object_type of circles using the class variable
print(Circle.object_type)
# Print the object_type of circle_1 and circle_2 using the instance variable (will also be 2)
print(circle_1.object_type)
print(circle_2.object_type)
plane
plane
plane
In the above code, I created two instances of Circle
class: circle_1
and circle_2
. Since object_type
is a class attribute, it has a shared value of "plane"
. You can access the class attribute without instances: Circle.object_type
. Class attributes are useful for defining properties that are common to all instances of the class.
5.1.2 Instance Attributes
Instance attributes belong to each instance and can have different values across instances. Since instance attributes belong to instances, you cannot access them directly from the class:
try:
print(Circle.center) # Circle class does not have "center".
except AttributeError as e:
print(e)
print(circle_1.center) # circle_1 is an instance and has an own value of "center"
print(circle_2.center) # circle_2 has a different value for its "center" attribute compared to circle_1
type object 'Circle' has no attribute 'center'
[0, 0]
[1, 1]
5.1.3 Conclusion to Instance Attributes
Now you have understood the differences between the class attributes and instance attributes:
TIPS: Class Attributes and Instance Attributes
- Class Attributes: Variables defined directly inside the class but outside of any methods
- They are shared by all instances of the class.
- They can be accessed without instances.
- Instance Attributes: Variables belong to each instance
- They can have different values throughout the instances.
5.2 Methods
Methods are functions defined within a class. In “Python” language, the term “method” has a different meaning in the different context, but we will focus on the methods as the functions defined in a class in the article.
First, let’s check the __init__()
method we have created in the Circle
class.
5.2.1 Constructor Method (__init__()
)
The __init__()
method is a special method in “Python” classes that is called when a new instance of the class is created. It is used to initialize the instance attributes. The syntax for the __init__()
method is as follows:
def __init__(self, param1, param2, ...):
# Initialize attributes here
self.param1 = param1
self.param2 = param2
# ...
As you have learned in Tip 1, the self
refers to the instance of the class itself and is used to access the attributes and methods of that instance. When you call the __init__()
method, the instance is passed to the method as an argument in “Python” language. So you need self
as the parameter of __init__()
:
class Circle:
object_type = "plane"
# Without "self"
def __init__(center, radius):
self.center = center
self.radius = radius
try:
# This raises TypeError
circle_1 = Circle([0, 0], 1)
except TypeError as e:
print("TypeError:", e)
class Circle:
object_type = "plane"
# With "self"
def __init__(self, center, radius):
self.center = center
self.radius = radius
# This raises no error
circle_2 = Circle([1, 1], 2)
TypeError: Circle.__init__() takes 2 positional arguments but 3 were given
In the first class, __init__()
method is defined without self
. At circle_1 = Circle([0, 0], 1)
, the code tries to pass the following three arguments to __init__()
:
circle_1
(the instance itself)[0, 0]
(forcenter
)1
(forradius
)
However, this __init__()
method is missing self
and has only two parameters: center
and radius
in the definition. Therefore circle_1 = Circle([0, 0], 1)
raises an error.
When you define the __init()__
method with self
, on the other hand, raises no error. So remember that you need self
when defining __init__()
method (See Figure 1).
As you will learn in Section 5.2.2, __init__()
belongs to the instance methods.
5.2.2 Instance Methods
When you need to work with instance-specific data, you can use instance methods:
import math
class Circle:
# Class attribute
object_type = "plane"
# Constructor method that initializes a Circle object with the coordinates of the center and the radius
def __init__(self, center, radius):
# Instance attributes
self.center = center
self.radius = radius
def __str__(self):
return f"Circle with a center at {self.center} and a radius of {self.radius}"
# Instance method for calculating the area of the circle
def area(self):
return math.pi * self.radius ** 2
circle_1 = Circle([0, 0], 1)
circle_1.area()
3.141592653589793
In the Circle
class, I added an instance method area()
. To define an instance method, you always need self
parameter as you did with __init__()
method.
TIPS: The Structure of an Instance Method
class ClassName: def instance_method(self, param1, param2, ...): # Method definition
Since the area of a circle is specific to each circle, I created the area()
method as the instance method. Using instance methods, you can use the instance-specific data (instance attributes).
Note that I also added __str__()
instance method to be used later on. Using the __str__()
method allows you to control how your objects are represented as strings, making it easier to display meaningful information about your objects when converted to strings or printed:
print(circle_1)
message = str(circle_1)
print(message)
Circle with a center at [0, 0] and a radius of 1
Circle with a center at [0, 0] and a radius of 1
5.2.3 Class Methods
When you need to perform operations that do NOT use data of a specific instance, you can use class methods. You can use a class method without creating a new instance. The structure of a class method is given here:
class ClassName:
@classmethod
def class_method(cls, param1, param2, ...):
# Method definition
To define a class method in “Python” programming, you can use the @classmethod decorator before the method definition. The class method takes cls
(the class itself) as the first parameter, similar to self
in the instance method.
Now the time to add a class method to our Circle
class:
class Circle:
# Class attribute
object_type = "plane"
# Constructor method that initializes a Circle object with the coordinates of the center and the radius
def __init__(self, center, radius):
# Instance attributes
self.center = center
self.radius = radius
def __str__(self):
return f"Circle with a center at {self.center} and a radius of {self.radius}"
@classmethod
def from_radius(cls, radius):
return cls(center=[0, 0], radius=radius)
# Instance method for calculating the area of the circle
def area(self):
return math.pi * self.radius ** 2
circle_1 = Circle.from_radius(1)
circle_2 = Circle.from_radius(2)
print(circle_1)
print(circle_2)
Circle with a center at [0, 0] and a radius of 1
Circle with a center at [0, 0] and a radius of 2
As I explained, from_radius()
method is defined after the @classmethod
decorator and it takes cls
as the first parameter. When you call the method, the Circle
class will be assigned to the cls
.
It will sometimes be useful to create a circle with a given radius at the origin. Since the from_radius()
method takes only radius, you have no need to pass the center coordinates. You need from_radius()
method before creating a new instance, so it is defined as a class method (Remember that you can use a class method without an instance).
5.2.4 Conclusion to Instance Methods and Class Methods
You have learned instance methods and class methods and compared them:
Tip 2: TIPS: Instance Methods and Class Methods
Instance Methods:
Table 1: Comparing Instance Methods and Class Methods
Methods For handling instance-specific data First Parameter Instance Method Yes self
Class Method No cls
(with@classmethod
decorator)
5.3 Setters and Getters
5.3.1 How to Define Setters and Getters
The Circle
class has two attributes, center
and radius
, that we can edit the values. As you have learned, we can set the values by Circle([0, 0], 1)
and get the values by circle_1.center
or circle_1.radius
. Then, what will happen when we assign negative values to the attributes?
circle_1 = Circle([-1, -1], -2)
print(circle_1)
Circle with a center at [-1, -1] and a radius of -2
Now the circle_1
represents a circle with a center at (-1, -1) and a radius of -2. The coordinates can take negative values, so (-1, -1) is a proper input. However, how about the radius? The radius is a distance between the center and the point on the circumference and it should take a non-negative value.
Therefore, we need a method to check if a correct value is passed to the attributes. The method is called a setter. After setting the proper value, you will need to get the value of the attribute. This is done by a method called a getter.
In “Python” programming, adding the @property
decorator makes a method_name
method to the getter, and @method_name.setter
creates the setter (Figure 2).
In Figure 2, the __init__()
method takes two parameters: self
and param1
. Inside the __init__()
method, param1
is assigned to an instance attribute self.param1
. When you write self.param1 = ...
, this always calls a setter method. This is useful when you validate the input before assigning it to the attribute.
The @property
decorator is used to define a method that works as a getter, in this case, param1
. The param1
method will return the value of the self.param1
attribute.
By using the same name as the param1
getter, @param1.setter
decorator defines a method to set the value of the self.param1
attribute. The setter will set the value of self.param1
when called.
This is an example of defining a setter and a getter, and there are other ways to do it. I recommend using the name of param1
in red boxes and using _param1
(with an underscore) in green boxes.
6 NOTE
The _
is used to indicate that the attribute is accessed only in the class. In other words, we will NOT access the value of _param1
by class_instance._param1
from outside of the class.
I summarize the setters and getters.
TIPS: How to Create a Setter and a Getter
- Getter:
- Use
@property
decorator- Setter:
- Use
@getter_name.setter
Getters and setters are methods but work like attributes. They are essentially methods, so you can use additional logic, to check the validity of the input value.
6.0.1 Adding Setters and Getters to the Circle
Class
Now, we will add the setters and getters to the Circle
class and modify some of the codes:
class Circle:
object_type = "plane"
def __init__(self, center, radius):
# Instance attributes
# No need to define self._center or self._radius here when using property.
# This is for the readability.
self.center = self._center = center
self.radius = self._radius = radius
def __str__(self):
return f"Circle with a center at {self._center} and a radius of {self._radius}"
# Getter for _radius
@property
def radius(self):
return self._radius
# Setter for _radius
@radius.setter
def radius(self, value):
if not value < 0:
self._radius = value
else:
raise ValueError("Radius must be non-negative")
# Getter for _center
@property
def center(self):
return self._center
# Setter for _center
@center.setter
def center(self, value):
self._center = value
@classmethod
def from_radius(cls, radius):
return cls(center=[0, 0], radius=radius)
def area(self):
return math.pi * self._radius ** 2
circle_1 = Circle([0, 0], 1)
circle_2 = Circle([1, 1], 2)
print("# Initialization")
print(circle_1)
print(circle_2)
# Set a radius of 4 using the setter
circle_1.radius = 4
print("\n# Edited the circle_1 radius")
print(circle_1)
print(circle_2)
# Get a center using the getter
print("\n# Getting the center and radius")
print(circle_1.center)
print(circle_1.radius)
# -3 is not a valid input for the radius, and the setter raises ValueError.
try:
circle_1.radius = -3
except ValueError as e:
print("\n# Error in Setter")
print(e)
try:
# Setters also work when initializing the instance
circle_3 = Circle([0, 0], -3)
except ValueError as e:
print("\n# Error in Constructor")
print(e)
try:
# This also triggers constructor and raises the error
circle_4 = Circle.from_radius(-3)
except ValueError as e:
print("\n# Error in from_radius")
print(e)
# Avoid directly referencing attributes prefixed with an underscore.
print(circle_1._radius)
circle_1._radius = -3
print(circle_1)
# Initialization
Circle with a center at [0, 0] and a radius of 1
Circle with a center at [1, 1] and a radius of 2
# Edited the circle_1 radius
Circle with a center at [0, 0] and a radius of 4
Circle with a center at [1, 1] and a radius of 2
# Getting the center and radius
[0, 0]
4
# Error in Setter
Radius must be non-negative
# Error in Constructor
Radius must be non-negative
# Error in from_radius
Radius must be non-negative
4
Circle with a center at [0, 0] and a radius of -3
In the __init__()
method, the input values are assigned to the instance attributes. When you write self.center = ...
, the setter method is called, and it checks the validity of the input.
The __init__()
method assigns the input to both self._center
and self._radius
. As I mentioned, the underscore, _
, before the attribute name implies that the attribute is accessed only in the class. These attributes will be used in the setter, getter, or other places in the class.
In Figure 2, there is no self._param1 = param1
in the __init__()
method. So in the Circle
class, we can rewrite the code like:
self.center = self._center = center
\rightarrowself.center = center
self.radius = self._radius = radius
\rightarrowself.radius = radius
Nevertheless, for the readability, I defined both self._center
and self._radius
in the __init__()
method. This is a confusing topic, and you do not need to care about it for now. Just remember that this __init__()
method can use the setter to validate the input and may improve some readability.
Then, I added the setters and getters for the instances. You will notice that the setter for the radius
has a logic to check if the non-negative value is passed to the instance. In the above code, I tried to pass -3
to the setter. Negative values are not proper for the radius, so the setter raised ValueError
.
In a nutshell, by using setters, you can add logic to check the input value, and the getters allow you to get the values of attributes.
NOTE
If you run
circle_1._radius
, you can directly access the instance attribute without the getter. Also,circle_1._radius = -3
can assign the negative value to the instance attribute even if there is the setter to check the input value.This is how the programming language works. With this in mind, you can simply write
circle_1.radius
orcircle_1.radius = -3
so that the code uses the setters and getters.
6.0.2 Conclusion to Setters and Getters
You have learned the setters and getters, and it is the time to review the concepts:
TIPS: Setters and Getters
Table 2: Comparing Setters and Getters
Method How to Define Purpose Setter Use @getter_name.setter
Validating and setting the input Getter Use @property
decoratorGetting the value of the attribute After checking the above basic knowledge of setters and getters, you review the difference between
radius
and_radius
.class Circle: def __init__(self, center, radius): self.radius = self._radius = radius # Getter for _radius @property def radius(self): return self._radius # Setter for _radius @radius.setter def radius(self, value): if not value < 0: self._radius = value else: raise ValueError("Radius must be positive") circle_1 = Circle([0, 0], 1) circle_1.radius = 2
The underscore,
_
, before the attribute name implies that the attribute is accessed only in the class. You don’t writecircle_1._radius = 2
to set the value from outside of the class or the instance. You should usecircle_1.radius = 2
. This way allows you to run the setter before assigning the input to the attribute.In the class, you will use
self._radius
instead ofself.radius
because the value stored inself._radius
has been validated by the setter and you do not need to run the setter again.
7 Exercise
Exercise 1
# Create a class "Rectangle" following the instructions:
# 1. The class has a class attribute "object_type". The value is ""plane"".
# 2. The class instance can be initiated with two parameters: "width" and "height".
# 2-1. The "width" and "height" should take non-negative values.
# 2-2. The "width" and "height" should be validated by the setters in the __init__() method.
# 3. The __init__() method has the following instance attributes:
# - "self.width" and "self._width"
# - "self.height" and "self._height"
# 4. The class has setters and getters: "width()" and "height()"
# 3-1. The getters simply return "self._width" or "self._height"
# 3-2. The setters validate that the input values are non-negative.
# 3-3. If the input is non-negative, the setters set the inputs to "self._width" or "self._height".
# 3-3. Otherwise, they raise a "ValueError" with the message "Width must be non-negative" or "Height must be non-negative".
# 5. The class has a class method "from_width()". This method takes the length of width as the input. So a square will be created.
# 6. The class has an instance method "area()". This returns to the area of the rectangle.
8 Conclusion
You have learned a lot about setters and getters in “Python” programming. There are other important concepts related to classes, such as inheritance and polymorphism. However, you have gained the knowledge and skills to delve into these advanced topics in the future.