Skip to content

Classes & Objects

A more generalized approach

We obviously need a better way to model more complex data. For that purpose we introduce the idea of an object. An object represents an amount of data and logic that belongs together in the context of a program. In our example each collected sample could be represented by an object:

Kroki

In the last episode we have seen, that each of these samples has the same kind of information attached to it, so we can describe what a sample in general looks like: We can summarize these common features as: “Every sample has an identifier and a collector”.

This abstraction is called a class. The “variables” that each object of the same class has are called attributes. In our case these are identifier and collector.

Kroki

An object that is of a certain class is called an instance of that class. Instantiation thus is the process of creating a new object from a class. All objects of a certain class will have the same attributes, but may have different values for each of the attributes; they might even be None.

Let us see how to write this down in Python, step-by-step:

We use the class keyword to define a new class:

# Here is the most minimal class
class Sample:
    pass  # Tell Python that this is all (for now)

my_sample = Sample()  # Create a new instance of Sample

But this minimal class does not contain any attributes. To add attributes to the class, we explicitly have to define a function which creates the attributes for the instance.

class Sample:

    # Tell python how to construct an object of that class
    def __init__(self, identifier, collector):
        self.identifier = identifier
        self.collector = collector

my_sample = Sample("0123", "Darwin")
# In the class version, this would be the function you call when instantiating a Sample(...)
def construct_sample(identifier, collector):
    self = {}
    initialize_sample(self, identifier, collector)
    return self

# This is the analog for the __init__ method
def initialize_sample(self, identifier, collector):
    self["identifier"] = identifier
    self["collector"] = collector

my_sample = initialize_sample("0123", "Darwin")

You will notice that this is generally very similar to how we created the dictionary, but also that there are some key differences. Instead of defining a function outside the data structure as with initialize_sample we defining an __init__ “function” inside this class. “Functions” inside the class are called methods. The __init__ method is called the constructor of the object, since it constructs the attributes of the class. Also, we are using the method parameter self to define and assign attributes to the object instead of the dictionary definition. We will later see that the self parameter always refers to the current object the method is working with.

The “dot” notation we used in the constructor to define the attributes can also be used to access and modify attributes of the object:

my_sample = Sample("0123", "Darwin")
my_sample.collector = "Irwin"

print("Object:", my_sample)
print("ID:", my_sample.identifier)
print("Collected by:", my_sample.collector)
my_sample = initialize_sample("0123", "Darwin")
my_sample["collector"] = "Irwin"

print("Object:", my_sample)
print("ID:", my_sample["identifier"])
print("Collected by:", my_sample["collector"])

Note how the output of printing an object directly is pretty awkward. Remember how we solved that problem in our last approach, but we will learn how to improve that later with classes.

There is one further important insight: Classes are data types. Try:

my_sample = Sample("0123", "Darwin")
print(type(my_sample))
<class 'Sample'>

This means that you can use any object as a value for variables or put them into a function as values for parameters. If we look at the type of other data types:

print(type(1.0))
print(type([1.0]))
print("Hello")
<class 'float'>
<class 'list'>
<class 'str'>

we will notice, that also all other data types in Python are classes.