Classes 1 (Nice)

Warning

The material in this ‘Nice’ chapter is optional. The discussion typically deals with content beyond what a novice should know. So, please finish all the ‘Need’ and ‘Good’ portions before you go through this chapter.

1 Classes

I committed a grave injustice in the ‘Need’ chapter by introducing classes as (just) a way to store data. In actuality, classes are much, much more. Classes support a different programming philosophy called object-oriented programming (OOP). The type of programming we do now is some form of functional or procdural programming.

OOP allows you to write better, reusable solutions. Using OOP can significantly reduce the complexity of the solution because we have reusable code, and often, it is easier to think in terms of objects. However, getting used to how objects work and the syntax of setting up a class takes some effort.

All this will sound very abstract right now. But, in keeping with the theme of this part, I will show you how to use a class to store data. Of course, this is not what classes should be used for. But I will slowly build on this in later chapters to give you a fuller picture.

I must also add that you already use classes every time you use Python. For example, when you use 'I am batman'.upper(), you use the class str. In fact, everything in Python is an object! Sorry, this will still be very cryptic at the moment. Let me use an example to explain the difference between a class and an object.

2 A particle class

2.1 Setting up a simple class

Let’s create a class to represent a particle. We will use this later to simulate it bouncing off a wall. To keep things simple, I will start with a 1D geometry. A particle typically has a mass, a radius (assuming it’s spherical) and a velocity. So, I can do the following.

class CParticle1D:
    mass=None
    position=None
    radius=None

2.2 Using the class

Now, let me use this class to create two particles.

particle_0=CParticle1D()        # Create a new particle
particle_0.mass=10             # Set mass for particle 0 
particle_0.position=.5         # Set position of particle 0
particle_0.radius=.01          # Set the radius of particle 0

particle_1=CParticle1D()        # Create another particle
particle_1.mass=20             # Set mass for particle 1 
particle_1.position=.1         # Set position of particle 1
particle_1.radius=.01          # Set the radius of particle 1

In the above snippet, particle_0 and particle_1 are instances of the class CParticle1D. particle_0 and particle_1 are also called objects of the type CParticle1D. This is similar to 1 being an object of the class int.

Remember

Remember that objects are instances of a class.

Once you have created your objects, you can use them in your other code. A trivial example is:

print(f'Particle 0 has mass {particle_0.mass} and is at position {particle_0.position}.')
Particle 0 has mass 10 and is at position 0.5.
print(f'Particle 1 has mass {particle_1.mass} and is at position {particle_1.position}.')
Particle 1 has mass 20 and is at position 0.1.

2.3 Class vs Object variables

Let’s say we want all the particles we create to be identical (i.e., same mass and size). Classes allow us to quickly implement this by adjusting the variable at the class level. These adjustments will then be visible to all the objects that are created.

Let’s see this in action.

CParticle1D.mass = 99           # Set the class variable mass
CParticle1D.radius = .01        # Set the class variable radius

particle_0=CParticle1D()        # Create a new particle
particle_0.position=.5         # Set position of particle 0
                               # This is the position variable
                               # of object 0

particle_1=CParticle1D()        # Create another particle
particle_1.position=.1         # Set position of particle 1
                               # This is the position variable
                               # of object 1

Now, let’s redo our trivial usage:

print(f'Particle 0 has mass {particle_0.mass} and is at position {particle_0.position}.')
Particle 0 has mass 99 and is at position 0.5.
print(f'Particle 1 has mass {particle_1.mass} and is at position {particle_1.position}.')
Particle 1 has mass 99 and is at position 0.1.

Notice the distinction between class variables and object variables. When you want to access the class variable, you use the . with the class name. Instead, if you wish to use the object variable, use the . with the object. What you do with the object does not affect the class or other objects (particles, in our case). However, there are subtleties to this that I don’t want to go into now. Let’s discover them later.

2.4 Making it more scalable

Typically we want to be able to expand a simulation to a more significant number of particles. Unfortunately, the previous way of creating particles is not very scalable. Here is a slightly better way.

# Make all the particles identical
CParticle1D.mass=99
CParticle1D.radius=.01

# Create a list of particles
all_particles=[CParticle1D()]
all_particles+=[CParticle1D()]
all_particles+=[CParticle1D()]

# Initialise with a random position
i = 0
all_particles[i].position=np.random.rand()

i += 1
all_particles[i].position=np.random.rand()

i += 1
all_particles[i].position=np.random.rand()

Here, I have used NumPy to create a random position.

Let’s see if it worked:

i = 0
print(f'Particle {i}: mass={all_particles[i].mass}, position= {all_particles[i].position:.3f}.')
Particle 0: mass=99, position= 0.715.
i += 1
print(f'Particle {i}: mass={all_particles[i].mass}, position= {all_particles[i].position:.3f}.')
Particle 1: mass=99, position= 0.885.
i += 1
print(f'Particle {i}: mass={all_particles[i].mass}, position= {all_particles[i].position:.3f}.')
Particle 2: mass=99, position= 0.469.

This is still grossly inefficient for a lazy person like me because it requires lots of typing. It can be made a lot simpler and less error-prone by using loops. I will show this to you later.

Back to top