Classes and Objects

Recall that in Python everything is an object. In this discussion we will see how to create programmer-defined objects.

An object is the foundation in Object Oriented Programming* for the storage of data. OOP is often referred to a data centric** because of this.

An object has attributes, which can be the variables that hold the data and the functions that act upon the data.

A function inside an object is referred to as a method.

Each object is an instance of a class, and we say that we instantiate an object from a class.

Programmer-Defined Types

Let's take a look at how to go about creating our own Type.

Our first type will be called Point that will represent a point in two-dimensional space.

Since a point is made up of two coordinates (x, y), we have several options for such a point, such as:

This third option is the more interesting choice and the one we will explore.

A programmer-defined type is called a class and looks like this:

class Point:
    '''Stores a point as an x,y pair.'''

The class header names the class, Point, in this case and the body consists of a docstring that explains the purpose of the class.

The attributes will be added shortly, but for now let's explore the class itself.

First, let's see what the type is.

Point is a class object in the global scope (__main__).

Notice how the type of Point is type. Every class is of this type, so think of this as the top level type.

An object is simply an instance of a type, so let's look at that next.

pt is a Point object in the global scope and stored in memory at the given address. Notice how the address starts with 0x which indicates hexadecimal notation.

The pt instance is of type Point.

Attributes

Unlike classes in other languages like Java or C++ which require that attributes be added when the class is defined, in Python you have a lot more flexibility.

You can add/remove attributes to a class skeleton as your needs change.

Let's add some attributes to hold some values.

First thing to notice is that you are able to access an instance's attributes directly. In Python there is no built-in mechanism to make attributes private. There are only conventional practices.

The object diagram for our point is:

point object diagram

Object as an argument

You may use an object like any other type, and so you may pass an instance as an argument to a function.

The initPoint() function accepts a Point instance and two values for x and y. The object referred to by pt is then updated.

The printPoint() function simply prints the instance in the tradional point notation (x, y).

Object as a return value

An instance may also be returned from a function.

Rectangles

Our next class will be the Rectangle class that represents a rectangle as a Point (lower-left) corner, and its size given as its width and height.

The Rectangle class is composed of another object type, Point. We call this composition and it creates a has-a relenionship between the Rectangle that has a Point.

The object graph for our Rectangle class looks like this now.

rectangle object graph

Objects are Mutable

Object mutability can be a bit tricky at first so let's review the concept.

The statement:

pt = Point()

create the variable pt, a reference to an instance of the Point class. Although we typically refer to the variable as the instance, in actuality pt is simply an object reference.

The object itself in what Point() returns.

So, there are two distict entities here, the reference and the instance. This sometimes confuses programmers when objects are passed as arguments to functions.

Let's examine both ideas closely with two examples. The first will demonstrate the pass by value mode which Python uses when passing arguments to functions.

Here we can see that the pt has not changed by the function noEffect(). When pt is passed as an argument it is copied into the parameter p.

Inside the function, the parameter p is assigned a new instance, but the argument pt still points to the original point (0.0, 0.0).

This is better shown with a stack diagram.

noEffect stack diagram

So, the change to the parameter itself, did not impact the argument in any way.

Now, since the object is actually mutable, we can change the instance itself.

Take a look at the next function that does just that.

In this case the instance itself is being updated, and thus the update is reflected in the argument itself.

The stack diagram for this case is:

hasEffect stack diagram

Copying

A more subtle and dangerous issue is the use of aliasing. When you assign one object to another, remember you are assigning the instance reference, thus you wind up with two references to the same object.

Both pt1 and pt2 below refer to the same point.

Notice how both objects are located at the same memory location. Any changes made to the instance via one of the references, will be felt by the other.

Aliasing makes your code harder to read and follow, and more error prone, so try and avoid it when possible.

Instead, try to copy the object instead. You can use the copy module and the copy() function.

The copy() function copies or duplicates the instance itself.

Notice now how the two objects are in different memory locations and a change to one object does not affect the other.

The y value for pt2 has not been changed.

Shallow copying

When you copy an object that has other objects, you must be careful of shallow copying.

A shallow copy simply copies the reference, it does not duplicate the instance as shown below.

Since the corner is a reference to a Point, the copy simply creates an alias and as such a change to the object via one of the references will be felt in the other.

Notice how the x coordinate has changed in both rect1 and rect2, but the width in rect1 has not.

Deep copying

If you want the change to remain local to the one object, use the deepcopy() function which actually copies the instance as well.

This time around the Point object referred to by corner has been duplicated and thus changes made to rect2 are not felt in rect1.

So, avoid aliasing and instead use copying, with deep copying being the safest and most likely intended.


Ex1: moveRectangle()

To get somee practice with deep copying, write a function moveRectangle() that returns a new Rectangle moved by some amount dx and dy.

Refer to my solution below if you have to, but give it an honest try before doing so.


Ex1: Solution - moveRectangle()

Practice problems

Create a separate Python source file (.py) in VSC to complete each exercise.


p1: circle.py

Write a definition for a class named Circle with attributes center and radius, where center is a Point object and radius is a number.

  1. Instantiate a Circle object that represents a circle with its center at (150, 100) and radius 75.

  2. Write a function named pointInCircle() that takes a Circle and a Point and returns True if the point lies in or on the boundary of the circle.

  3. Write a function named rectInCircle() that takes a Circle and a Rectangle and returns True if the rectangle lies entirely in or on the boundary of the circle.

  4. Write a function named rectCircleOverlap() that takes a Circle and a Rectangle and returns True if any of the corners of the rectangle fall inside the circle. Or as a more challenging version, return True if any part of the rectangle falls inside the circle.

[Files] [TOC] [Classes And Functions]