Seminar 006: Object-Oriented Programming¶
Notes¶
Classes in Python¶
In the following, we will explore HOW the process of instantiation of a class works. Please note, the code is just for demo purposes and thus please ignore all the utterly technical terms that are marked as unnecessary. In general, the process of instantiation can be summarized in the following diagram.
number_of_instances = 0
class Demo:
def __new__(cls):
"""
Whenever you call a "class" this method will be executed.
It is NOT important for your use cases, but it shows you
how the process works internally.
"""
# Disregard this, its for demo only
number_of_instances = globals()["number_of_instances"]
globals()["number_of_instances"] += 1
print(f"Instance {number_of_instances} of the class has been created!")
# This will create a DNASequence INSTANCE
# There can be as many instances as you wish
self = super(Demo, cls).__new__(cls)
self.number_of_instance = number_of_instances # type: ignore
return self
def __init__(self):
"""
In this method, the previously created "self" is passed,
such that we now can add new attributes to it. Similar,
to the example provided in the slides.
The arguments of the "__init__" are simply the gateway
to assign some values to the attributes. In this method
we are just making sure that they will be integrated.
Think about the "__init__" as the worker who transfers
your information to the "architect" that will build your
house from the "blueprint" that we have defined in the classs.
"""
print(
f"We are now in the __init__ method of instance {self.number_of_instance}" # type: ignore
)
"""
PLEASE NOTE
The most important method is __init__ and matters most. The other one
is just for more advanced topics and is useed here to demonstrate to you
how an instance is created.
"""
Classes serve as blueprints from which we can create multiple instances. Each instance shares the same structure (attribute names) but differs in content (attribute values). In this example, our Demo class allows us to create multiple independent instances, each with its own identity (ID) and state, while maintaining the same blueprint structure.
for _ in range(2):
instance = Demo()
print("ID of the instance: ", id(instance))
Instance 0 of the class has been created! We are now in the __init__ method of instance 0 ID of the instance: 4505057088 Instance 1 of the class has been created! We are now in the __init__ method of instance 1 ID of the instance: 4505057184
The IDs show you, that something new is created each time we call the Demo class using Demo(). Thus, each instance we create can act on their own
# Here is an example that may demonstrate to you how
# to apply the concept onto our application
class DNASequence:
def __init__(self, sequence):
"""
Here we define the inputs to the class, which we will use
to set up the attributes. You will see that the input does
not have to all the attributes, but just those that are
necessary.
In this case, we only need the sequence, because we can
already calculate the GC_CONTENT using the sequence itself.
The "self" parameter now is the INSTANCE of the class, the unqiue
one with the ID that we want to create. We are now just adding
the necessary attributes.
"""
print(">>> New instance: ", id(self))
print(">>> Entering '__init__ method'")
self.sequence = sequence # Is now added to the class
self.gc_content = (sequence.count("G") + sequence.count("C")) / len(sequence)
print(f"\n#### Has attributes {self.__dict__}\n")
# Lets use the class and create a first instance
sequence1 = DNASequence(sequence="ATGCGCG")
print("This is the ID of the instance in the variable: ", id(sequence1))
>>> New instance: 132515875497024
>>> Entering '__init__ method'
#### Has attributes {'sequence': 'ATGCGCG', 'gc_content': 0.7142857142857143}
This is the ID of the instance in the variable: 132515875497024
# Lets do another one
sequence2 = DNASequence(sequence="GCGCGGC")
print("This is the ID of the instance in the variable: ", id(sequence2))
>>> New instance: 132515875495536
>>> Entering '__init__ method'
#### Has attributes {'sequence': 'GCGCGGC', 'gc_content': 1.0}
This is the ID of the instance in the variable: 132515875495536
Conclusion¶
We have set up a DNASequence class, which in its essence is a blueprint of ...
- What is needed to create a DNA Sequence object? --> Arguments of the
__init__ - How can we set up the attributes?
- Simple assignment from the argument to the attribute -->
self.sequence = sequence - Or do something with argument, where the result is assigned to an attribute -->
self.gc_content
- Simple assignment from the argument to the attribute -->
With this type of "recursion", we can now create DNASequence objects by passing the sequence argument and its appropriate value (the gene). In return we store the sequence of course, but we also calculate the GC content since we have specified this in our __init__-method.
This is the power of object-oriented programming, because we can be explicit about what makes up a DNA sequence without the user having to do all these calculations for us. In addition, we could derive far more information and all the user has to do is pass a sequence. Ultimately, the user can now access all the attributes and do something nice with it.
In the next seminar, you will learn how to extend the functionality of a class, by adding "actions" to it, which will turn a class into a multifunctional tool ⚙️