../_images/book_cover.jpg

This notebook contains an excerpt from the Python Programming and Numerical Methods - A Guide for Engineers and Scientists, the content is also available at Berkeley Python Numerical Methods.

The copyright of the book belongs to Elsevier. We also have this interactive book online for a better learning experience. The code is released under the MIT license. If you find this content useful, please consider supporting the work on Elsevier or Amazon!

< 7.1 Introduction to OOP | Contents | 7.3 Inheritance, Encapsulation and Polymorphism >

Class and Object

The previous section introduced the two main components of OOP: Class, which is a blueprint used to define a logical grouping of data and functions, and Object, which is an instance of the defined class with actual values. In this section, we will get into greater detail of both of these components.

Class

A class is a definition of the structure that we want. Similar to a function, it is defined as a block of code, starting with the class statement. The syntax of defining a class is:

class ClassName(superclass):
    
    def __init__(self, arguments):
        # define or assign object attributes
        
    def other_methods(self, arguments):
        # body of the method

Note: the definition of a class is very similar to a function. It needs to be instantiated first before you can use it. For the class name, it is standard convention to use “CapWords.” The superclass is used when you want create a new class to inherit the attributes and methods from another already defined class. We will talk more about inheritance in the next section. The __init__ is one of the special methods in Python classes that is run as soon as an object of a class is instantiated (created). It assigns initial values to the object before it is ready to be used. Note the two underscores at the beginning and end of the init, indicating this is a special method reserved for special use in the language. In this init method, you can assign attributes directly when you create the object. The other_methods functions are used to define the instance methods that will be applied on the attributes, just like functions we discussed before. You may notice that there is a parameter self for defining this method in the class. Why? A class instance method must have this extra argument as the first argument when you define it. This particular argument refers to the object itself; conventionally, we use self to name it. Through this self parameter, instance methods can freely access attributes and other methods in the same object. When we define or call an instance method within a class, we need to use this self parameter. Let us see an example below.

EXAMPLE: Define a class named Student, with the attributes sid (student id), name, gender, type in the init method and a method called say_name to print out the student’s name. All the attributes will be passed in except type, which will have a value as ‘learning’.

class Student():
    
    def __init__(self, sid, name, gender):
        self.sid = sid
        self.name = name
        self.gender = gender
        self.type = 'learning'
        
    def say_name(self):
        print("My name is " + self.name)

From the above example, we can see this simple class contains all the necessary parts mentioned previously. The __init__ method will initialize the attributes when we create an object. We need to pass in the initial value for sid, name, and gender, while the attribute type is a fixed value as “learning”.

These attributes can be accessed by all the other methods defined in the class with self.attribute, for example, in the say_name method, we can use the name attribute with self.name. The methods defined in the class can be accessed and used in other different methods as well using self.method. Let us see the following example.

TRY IT! Add a method report that print not only the student name, but also the student id. The method will have another argument score, that will pass in a number between 0 - 100 as part of the report.

class Student():
    
    def __init__(self, sid, name, gender):
        self.sid = sid
        self.name = name
        self.gender = gender
        self.type = 'learning'
        
    def say_name(self):
        print("My name is " + self.name)
        
    def report(self, score):
        self.say_name()
        print("My id is: " + self.sid)
        print("My score is: " + str(score))

Object

As mentioned before, an object is an instance of the defined class with actual values. We can have many instances of different values associated with the class, and each of these instances will be independent with each other as we saw previously. Also, after we create an object, and call this instance method from the object, we do not need to give value to the self parameter since Python automatically provides it. See the following example:

EXAMPLE: Create two objects (“001”, “Susan”, “F”) and (“002”, “Mike”, “M”), and call the method say_name.

student1 = Student("001", "Susan", "F")
student2 = Student("002", "Mike", "M")

student1.say_name()
student2.say_name()
print(student1.type)
print(student1.gender)
My name is Susan
My name is Mike
learning
F

In the above code, we created two objects, student1 and student2, with two different sets of values. Each object is an instance of the Student class and has a different set of attributes. Type student1.+TAB to see the defined attributes and methods. To get access to one attribute, type object.attribute, e.g., student1.type. In contrast, to call a method, you need the parentheses because you are calling a function, such as student1.say_name().

TRY IT! Call method report for student1 and student2 with score 95 and 90 individually. Note: we do not need the “self” as an argument here.

student1.report(95)
student2.report(90)
My name is Susan
My id is: 001
My score is: 95
My name is Mike
My id is: 002
My score is: 90

We can see both methods calling to print out the data associated with the two objects. Note: the score value we passed in is only available to the method report (within the scope of this method). We can also see that the method say_name call in the report also works, as long as you call the method with the self in it.

Class vs instance attributes

The attributes we presented above are actually called instance attributes, which means that they are only belong to a specific instance; when you use them, you need to use the self.attribute within the class. There are another type of attributes called class attributes, which will be shared with all the instances created from this class. Let us see an example how to define and use a class attribute.

EXAMPLE: Modify the Student class to add a class attribute n, which will record how many object we are creating. Also, add a method num_instances to print out the number.

class Student():
    
    n_instances = 0
    
    def __init__(self, sid, name, gender):
        self.sid = sid
        self.name = name
        self.gender = gender
        self.type = 'learning'
        Student.n_instances += 1
        
    def say_name(self):
        print("My name is " + self.name)
        
    def report(self, score):
        self.say_name()
        print("My id is: " + self.sid)
        print("My score is: " + str(score))
        
    def num_instances(self):
        print(f'We have {Student.n_instances}-instance in total')

In defining a class attribute, we must define it outside of all the other methods without using self. To use the class attributes, we use ClassName.attribute, which in this case is Student.n. This attribute will be shared with all the instances that are created from this class. Let us see the following code to show the idea.

student1 = Student("001", "Susan", "F")
student1.num_instances()
student2 = Student("002", "Mike", "M")
student1.num_instances()
student2.num_instances()
We have 1-instance in total
We have 2-instance in total
We have 2-instance in total

As before, we created two objects, the instance attribute sid, name, but gender only belongs to the specific object. For example, student1.name is “Susan” and student2.name is “Mike”. But when we print out the class attribute Student.n_instances after we created object student2, the one in the student1 changes as well. This is the expectation we have for the class attribute because it is shared across all the created objects.

Now that we understand the difference between class and instance, we are in good shape to use basic OOP in Python. Before we can take full advantage of OOP, we still need to understand the concept of inheritance, encapsulation and polymorphism. Let us start next section!

< 7.1 Introduction to OOP | Contents | 7.3 Inheritance, Encapsulation and Polymorphism >