Python: Fundamentals of Object-Oriented Programming (OOP)

Object-oriented programming involves the presence of classes, objects, and the interaction of objects with each other, resulting in changes to their properties.

A class is an abstract data type. A class describes an entity (its characteristics and possible actions). For example, a class can describe a student, a car, etc. After describing a class, we can create its instance – an object. An object is a concrete instance (representative) of a class. An object in a program can only be created based on a class.

Classes can be located either at the beginning of the program code or imported from other module files (also at the beginning of the code).

Creating a Class

To create classes, the class statement is used. It consists of a header line and a body. The header consists of the keyword class and the class name. After the class name, you can specify the names of superclasses in parentheses, on the basis of which the class is created. We will create our classes without superclasses, so we will not put parentheses at the end.

The body of the class consists of a block of statements. The body must be indented according to Python syntax.

Class attributes are the names of variables outside functions (fields) and the names of functions. These attributes are inherited by all objects created based on this class. Attributes characterize the properties and behavior of an object. Objects can have attributes created within the method body if this method is called for a specific object.

Attributes of an instance of a class are accessed using a dot.

The general structure of a class can be represented as follows:

Code: Class OOP

'''
Class < class name >[(< superclasses >)]:
    < variable1 > = < value1 >
    < variable2 > = < value2 >
    ...
    < variable n > = < value n >
    
    def < method name>([self, < parameters >]):
        self.< variable > = < value >
'''



class Student:
    '''
    structure for storing information
    about an object "student"
    '''

    full_name = 'Not specified'
    sex = 'Not specified'
    age = 0
    faculty = 'Not specified'
    year = 0
    specialty = 'Not specified'
    group = 'Not specified'

Creating an Object - Objects (instances of a class) are created as follows:

< variable_name > = < class_name >( )

The parentheses are mandatory. After executing such a command, an object appears in the program, accessible by the variable name associated with it. When created, the object inherits the attributes of its class, meaning objects possess the characteristics defined in their classes.

The number of objects that can be created based on a class is unlimited.

Objects of the same class have a similar set of attributes, but the values of these attributes can differ for each object. In other words, objects of the same class are similar but individually distinct.

Example: Let's create an object of the Student class:

st = Student( )

All the properties of the object st will take the default values specified in the class description. Thus, we created a class and based on it, we created one object. The number of objects can be any, and objects can be added to lists, tuples, and so on.

Changing and Accessing Attributes

After creating an object, you can access its properties using dot notation. Let's work with the same object st – an instance of the student class.

Suppose we need to display the full name and age properties for the object st. We do the following:

Code: Class Object (instance)

print('Full Name =', st.full_name)
print('Age =', st.age)

'''
Notice that the fields have received 
the default values specified in the class description
'''
# Full Name = Not defined
# Age = 0

Now we need to change the properties. The process is similar, but now we assign new values.

Example:

We need to change the full name of the object:

Code: Class Object (instance)

st.full_name = 'Ivanov Ivan Ivanovich'
print(st.full_name)

'''
In this example, we changed the field
value directly in the code.
Now let's set a new full name
from the keyboard after starting the program:
'''

st.full_name = input('Enter the new full name for the object: ')

Methods OOP

Methods in classes are essentially functions. The difference is that methods take one mandatory parameter – self. This parameter is needed to reference the object.

Methods can modify existing properties of an object, create new properties (though this is generally not recommended in practice), and perform other actions on objects. The method needs to "know" which object's data it is supposed to process. For this, the name of the variable associated with the object (or a reference to the object) is passed as the first (and sometimes the only) argument. In the class definition, the self parameter is used to indicate the object being passed.

To call a method for a specific object in the main block of the program, the syntax is as follows:

< object >.< method_name (...) >

Here, object refers to the variable associated with it. This expression is transformed within the class to which the object belongs into the expression:

< method_name(object, ...) >

That is, the specific object's name replaces the self parameter.

Example:

Let's continue working with the same student class. Since a student changes their course each year, and may change their specialty or group, we will write methods for these purposes. Additionally, we'll create a separate method to display the object's properties on the screen.

Methods are defined in the class description:

Code: Methods

class Student:
    full_name = 'Not defined'
    sex = 'Not defined'
    age = 0
    faculty = 'Not defined'
    year = 0
    specialty = 'Not defined'
    group = 'Not defined'
                            
    def set_course(self, new_year):
        self.year = new_year
                            
    def set_specialty(self, new_specialty):
        self.specialty = new_specialty
    
    def set_group(self, new_group):
        self.group = new_group
    
    def upyear(self):
        self.year += 1 
    
    def display_info(self):
        print(f'Full Name: {self.full_name}')
        print(f'Sex: {self.sex}')
        print(f'Age: {self.age}')
        print(f'Faculty: {self.faculty}')
        print(f'Year: {self.year}')
        print(f'Specialty: {self.specialty}')
        print(f'Group: {self.group}')
                    
# Creating an object
st = Student()
                    
# Displaying default values
st.display_info()
                    
# Updating attributes
st.full_name = 'Ivanov Ivan Ivanovich'
st.sex = 'male'
st.age = 19
st.faculty = 'Faculty of Cybersecurity'
st.set_course(2)
st.set_specialty('Computer Science')
st.set_group('CS-101')
                    
# Displaying updated values
st.display_info()


# Output:
# Full Name: Not defined
# Sex: Not defined
# Age: 0
# Faculty: Not defined
# Year: 0
# Specialty: Not defined
# Group: Not defined

# Full Name: Ivanov Ivan Ivanovich
# Sex: male
# Age: 19
# Faculty: Faculty of Cybersecurity
# Year: 2
# Specialty: Computer Science
# Group: CS-101

These methods do not require any additional parameters. And the methods for changing a specialty set_specialty and changing a group set_group require at least one parameter.

This is an indication of a new specialty and a new group. We must specify the parameters in in parentheses, separated by commas after self.

Constructors

Previously, we discussed that when an instance of a class (object) is created, the field values are set to default values, which can be changed later. However, this is not always convenient. Often, it is more appropriate to specify the values of some fields immediately upon creating an instance of the class. For this purpose, we use a class constructor.

A constructor is a method that is called automatically when an object is created. In it, you need to create the class properties if they were not already defined. The constructor in Python always has the name __init__. Note that there are two underscores at the beginning and the end. Remember that the first parameter in a method is self, and the constructor is no exception. The constructor should be written at the very beginning of the class.

Code: Class Object (instance)

'''
def __init__(self, < parameter1 >, < parameter2 >, ..., < parameterN >):
    self.< attribute1 > = < parameter1 >
    self.< attribute2 > = < parameter2 >
    ...
    self.< attributeN > = < parameterN >
'''

def __init__(self, full_name='Not defined', sex='Not defined', Age=0, 
             faculty='Not defined', year=0, specialty='Not defined', group='Not defined'):
    self.full_name = full_name
    self.sex = sex
    self.age = Age
    self.faculty = faculty
    self.year = year
    self.specialty = specialty
    self.group = group

Parameter names are the same as attribute names, only written with a lower or capital letter, example Age. They do this for convenience. But in fact the names parameters can be anything, but not the same as the attribute names.

Attempt to Create an Object Without Initial Values

An attempt to create an object without specifying the initial field values can lead to an error if default values are not provided in the constructor.

def __init__(self, Full_name, Sex, Age, Faculty, Year, Specialty, Group):

Code: Class Object (instance)

class Student:
    # Constructor
    def __init__(self, Full_name, Sex, Age, Faculty, Year, Specialty, Group):
        self.full_name = Full_name
        self.sex = Sex
        self.age = Age
        self.faculty = Faculty
        self.year = Year
        self.specialty = Specialty
        self.group = Group

    # Method to advance to the next year
    def up_year(self):
        self.year += 1

    # Method to print all object properties
    def prop_print(self):
        print('Full name =', self.full_name)
        print('Sex =', self.sex)
        print('Age =', self.age)
        print('Faculty =', self.faculty)
        print('Year =', self.year)
        print('Specialty =', self.specialty)
        print('Group =', self.group)

    # Method to change the group
    def change_group(self, new_group):
        self.group = new_group

    # Method to change the specialty
    def change_spec(self, new_spec):
        self.specialty = new_spec


st = Student('Petrov N.P.', 'male', 18, 'Cybersecurity', 1, 'Computer Science', 'CS-101')
st.prop_print()


# Full name = Petrov N.P.
# Sex = male
# Age = 18
# Faculty = Cybersecurity
# Year = 1
# Specialty = Computer Science
# Group = CS-101
    

Links: python documentation

[1] Class Objects
[2] Class and Instance Variables