Everything is an Object in Python

Aims

  • Understand what an object is in Python
  • Familiarise with the notion of object, instances, attributes and methods

Learning outcomes

  • Creating basic classes in Python

Objects

In C/C++ you have seen many kinds of data structures: arrays, structs, pointers, linked lists, etc. They are fundamentally different ways to organise data in memory.

In Python, instead, any structure is fundamentally the same: it is an abstract entity called an object.

Objects in Python can be specialised, and so we can have objects of different types (which we can check with the type() function). But fundamentally everything is an object.

What are the characteristics of an object?

Objects are simple. They contain:

  • Attributes: pieces of data that describe the object
  • Methods: functions that operate on the object

And that’s it!

Syntactically this means one thing: that given a variable in python, we can always check its attributes and methods using the . operator (something that we insisted on the first year).

Try this in any python shell.

A full list of methods and attributes can also be obtained with the dir() function:

This will print a long list of methods and attributes that you can use with the variable a_number.

Attributes

Attributes are pieces of data that describe the object. For example, a string object has an attribute length, which tells you how many characters are in the string. You can access attributes using the . operator:

Methods

Methods are functions that operate on the object. For example, a list object has a method append(), which adds an element to the end of the list. You can call methods using the . operator followed by parentheses:

Custom Classes

The above examples are trivial and based on bult-in types. But we have the same for objects imported from modules (e.g. numpy arrays). This is because modules define custom classes.

Since in Python everything is an object, its syntax makes it very easy to define our own custom classes.

Example: A Particle Class

Let’s define a simple class to represent a particle in 2D space. The particle will have attributes for its position and velocity, and methods to update its position based on its velocity.

To define classes in Python we need a few syntactical conventions:

  • a new keyword to define a class: class
  • indentation to define the scope of the class and its methods (no curly braces)
  • a special method called __init__ to define the constructor of the class, i.e. the function that actually creates the object in memory when needed.

This is a very minimal class definition. The name of the class is Particle. The constructor __init__ is a function that takes at least one input: self. This is a reference to the object itself, and it is used to define attributes and methods that belong to the object.

Encapsulation

Why do we need self? The idea is that we will create a variable of class Particle in the following way:

here the syntax Particle() calls the __init__ method (i.e. function) of the class Particle, and p is the variable that will hold the object in memory. It will be run only when the interpreter reaches that line. When it is run, the object p exists and needs to store all its attributes/ How can we refer to p and p only in our class definition? The answer is self: when the constructor is called, self will be a reference to p.

Why do we need a reference to p? because we want all the properties of p to be stored inside the object itself. This is called encapsulation.

So, in the rest of this workshop, you will try and create your own classes in Python to familiarise with the idea.

For this purpose, working inside a Jupyter notebook (or VSCode with Jupyter extension) is probably the best way to go, since you can test your code interactively.

Instances

Our initial example is trivial: the initial position are always (0,0). Let’s make it more interesting by allowing the user to specify the initial position when creating the object.

Now we can create two particles with different initial positions:

And we can access their attributes:

So, while p1 and p2 are both objects of class Particle, they are different objects with their own attributes. These are different instances of the same class.

More methods

At the moment our class only has a constructor that takes some attributes, but it doesn;t do much more. Let’s add a method to update the position of the particle based on a velocity.

Now we can create a particle and move it:

This will update the position of the particle p by adding the velocity components to its current position.

NoteTask 1: Create your Particle class

In a suitable Python file, create a class Particle that has:

  • a constructor that takes initial position x0 and y0 as parameters
  • a method move(vx, vy) that updates the position of the particle based on the velocity components vx and vy
  • a method get_position() that returns the current position of the particle as a tuple (x, y)

Combining objects together

Given Python’s simplicity it is then immediate to create objects together to create more complex structures. For example, we can readily create many particles using a for loop and store them in a list:

Notice that here we used the methods of the built-in object list together with our custom class Particle.

NoteTask 2: Combining classes

Create a new class called ParticleSystem that contains a list of Particle objects. The class should have:

  • a constructor that initializes an empty list of particles
  • a method add_particle(x0, y0) that creates a new Particle object with the given initial position and adds it to the list
  • a method move_all(vx, vy) that moves all particles in the system by the given velocity components
  • a method get_positions() that returns a list of the current positions of all particles in the system
NoteTask 3: Using the classes and visualisation

Now write a small program that uses your classes to instantiate a system of 10 particles with random initial positions, moves them with random velocities, and prints their final positions.

Use matplotlib to visualise the positions during the simulation and to produce a complete animation. This should be integrated within the ParticleSystem class as a method animate(steps) that runs the simulation for a given number of steps and produces an animation of the particles’ movements.

Matplotlib’s FuncAnimation class can be useful for this purpose.

Here is a simple example of how to use FuncAnimation:

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')  
def init():
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 10)
    return ln,
def update(frame):
    xdata.append(np.random.rand())
    ydata.append(np.random.rand())
    ln.set_data(xdata, ydata)
    return ln,
ani = FuncAnimation(fig, update, frames=range(100), init_func=init, blit=True)
plt.show()