19. Objects I — Introduction

  • Objects are a way to combine data and functionality together within our programs

  • Motivation for this is, it may help with abstraction and encapsulation

  • Further, you may find it a little more natural and analogous to how you think of the world

  • Consider the Lists you have been using — these are objects

    • They have some data associated with them,

      • Their size

      • the information stored in the list

    • But they also have functionality associated with them too

      • append

      • remove

  • You are already used to working with some objects (e.g., List)

  • But you have not defined your own objects

  • In this topic, the basics of how to define your own objects will be covered

19.1. Classes vs Objects

  • We are going to make Circle objects

  • However, in order to start making our own objects, we first must define a class

  • A class is simply the code that defines what it means for something to be an object of the class

  • For example, we will define a Circle class, which contains all the information needed for Python to start making instances of Circles

    • You can think of the class as the blueprints for making objects

  • Going back to Lists

    • There exists a class definition (blueprints) for Lists

    • With the one class, I can then create as many instances of Lists as I want

19.2. Circle Class

  • Just like the rest of the class, we will lean by doing

  • In other words, we will define the Circle class and then make a few instances of Circle objects

  • However, there is one big question — what is a circle; what data and functionality should it have?

    • Turns out, we get to invent this

    • We decide what data will be stored

    • We decide what functionality it will have

  • For our needs, we will have a Circle:

    • Know its radius — this is an attribute, the data

    • Be able to perform basic geometry calculations — these are methods, the functionality

19.2.1. Starting the Class

  • To start writing our Circle class, we use the class keyword

1class Circle:
2    """
3    A class for representing circle based on its radius. The class provides functionality to perform basic geometry
4    calculations (diameter, area, circumference).
5    """
  • The above example contains the start of the class

  • The class Circle: starts the class

    • Anything indented is part of the Circle class definition

    • Like how anything indented under an if is part of the if block

  • The docstring comment is a simple description of the class

19.2.2. Constructor/Initialization and Attributes

  • With just the class Circle, we can actually start making instances of the class

1some_circle = Circle()
2print(type(some_circle))    # Results in something like <class '__main__.Circle'>
  • However, this is not particularly useful as we have yet to really describe the Circle class

19.2.2.1. Constructor

  • To start making the class useful, we will write a special function that tells Python how to setup the class for our needs

 1class Circle:
 2    """
 3    A class for representing circle based on its radius. The class provides functionality to perform basic geometry
 4    calculations (diameter, area, circumference).
 5    """
 6
 7    def __init__(self):
 8        """
 9        Creates a Circle object with a radius of 0.
10        """
11        self.radius = 0
  • In the above example, we see the use of the special function called __init__, which describes how to initialize an instance of the class

    • The __init__ method is called the constructor, or the initialization method

  • We can also see that we are creating an attribute called radius that will have the value 0 upon the creation of a Circle object

  • You will notice a special variable called self in the parameter list and before the attribute radius

    • self is a reference variable to this, the current instance of the class (itself)

    • All methods within the class require that the first parameter is the self reference variable

    • Additionally, accessing any attributes or methods within the class require the use of the self reference variable

    • The self variable can feel a little weird at first, but it is something that will start to make sense as we go

  • With __init__ written with the setup of the attribute radius, we can now start to assign values

1circle_a = Circle()
2circle_b = Circle()
3
4circle_a.radius = 1
5circle_b.radius = 5
6
7print(circle_a.radius)      # Results in 1
8print(circle_b.radius)      # Results in 5
  • In the above example, we created two Circle objects and then assigned a value to their respective radius attributes

  • Both circle_a and circle_b are of the class Circle, but they are two separate instances of the class with two separate radius attributes

19.2.2.2. Constructor Parameters

  • We can also include parameters for the __init__ method, as seen below

 1class Circle:
 2    """
 3    A class for representing circle based on its radius. The class provides functionality to perform basic geometry
 4    calculations (diameter, area, circumference).
 5    """
 6
 7    def __init__(self, radius: float):
 8        """
 9        Creates a Circle object with the specified radius.
10
11        :param radius: The radius of the Circle
12        :type radius: float
13        """
14        self.radius = radius
  • In the above example, we include a parameter for the radius, which will be used to set the attribute when a Circle object is created

  • This way we do not need to set the values ourselves after they are created, as seen in the following example

1circle_a = Circle(1)
2circle_b = Circle(5)
3
4print(circle_a.radius)      # Results in 1
5print(circle_b.radius)      # Results in 5

19.2.3. Functionality and Methods

  • The Circle class has the attribute radius. but as of now, that’s all it can do — store a radius value

  • Further, there are other features of a circle we may want to capture

    • Diameter of a circle

    • Area of a circle

    • Circumference of a circle

  • Fortunately, although these values are not stored within the Circle class as attributes, they can be calculated based on the Circle's radius

  • Consider the diameter method below that we could add to the Circle class below __init__

 1class Circle:
 2
 3    # init and/or other methods not shown for brevity
 4
 5    def diameter(self) -> float:
 6        """
 7        Calculate and return the diameter of the Circle based on its radius.
 8
 9        :return: diameter of the Circle
10        :rtype: float
11        """
12        return 2 * self.radius
  • The method itself is not overly sophisticated — the diameter of a circle is twice its radius

  • But you will notice, once again, the use of self

    • Every method that belongs to the class must start with self in the parameter list

    • Since we are accessing the specific Circle object’s radius, we make use of the reference variable self

  • The following two methods follow the same pattern, but perform their respective calculations

 1class Circle:
 2
 3    # init and/or other methods not shown for brevity
 4
 5    def area(self) -> float:
 6        """
 7        Calculate and return the area of the Circle based on its radius.
 8
 9        :return: Area of the Circle
10        :rtype: float
11        """
12        return math.pi * self.radius**2
13
14    def circumference(self) -> float:
15        """
16        Calculate and return the circumference of the Circle based on its radius.
17
18        :return: Circumference of the Circle
19        :rtype: float
20        """
21        return 2 * math.pi * self.radius
  • To make use of these methods, we call the method on the specific Circle object we want

1circle_a = Circle(1)
2circle_b = Circle(5)
3
4print(circle_a.area())              # Results in 3.141592653589793
5print(circle_b.circumference())     # Results in 31.41592653589793
  • In the above example, notice how the methods are called with parentheses

    • Also notice that, although the methods in the Circle class have the self variable specified in its parameter list, no actual value is explicitly passed as an argument

  • When calling circle_a.area(), I am asking the Circle object referenced by circle_a to calculate and return its area

  • Similarly, when calling circle_b.circumference(), I am asking the instance circle_b to calculate and return its circumference

Note

Consider circle_a.area(). The variable circle_a is a reference to some Circle object, and I am asking for that instance to calculate and return its area. Here, circle_a and the self variable from within the Circle class are referencing the same Circle object. When looking at the area method’s code, it makes use of its attribute radius, which is accessed via a reference variable to the instance of the Circle object the method was invoked on — self.

19.2.4. Testing the Class

  • As always, we want to ensure our programs are correct, so we will write some tests

  • Unlike the assert tests we have done so far, we need to create instances of the objects first before we can test them

 1circle_0 = Circle(0)
 2assert 0 == circle_0.radius
 3assert 0 == circle_0.diameter()
 4assert 0 == circle_0.area()
 5assert 0 == circle_0.circumference()
 6
 7circle_10 = Circle(10)
 8assert 10 == circle_10.radius
 9assert 20 == circle_10.diameter()
10assert 0.001 > abs(circle_10.area() - 314.1592)
11assert 0.001 > abs(circle_10.circumference() - 62.8319)

19.3. For Next Class