Seminar 006: Object-Oriented Programming¶
Notes¶
# What is Try-except?
number = [1,2,3,4]
# Accessing an index that is not given will result in an
# IndexError
number[4]
# Code that follows will not be executed and
# the program crashes
numbers = "jhdjf"
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-11-305f4a276c09> in <cell line: 6>() 4 # Accessing an index that is not given will result in an 5 # IndexError ----> 6 number[4] 7 8 # Code that follows will not be executed and IndexError: list index out of range
try:
# By wrapping our code in 'try' we can ...
number[4]
except IndexError:
# ... catch a specific error and do something
print("You wanted to retrieve an index, that is out of bounds")
print(f"But I will give you the last one {number[-1]}")
# This code will still run and our program does not crash
print("Has run!")
You wanted to retrieve an index, that is out of bounds But I will give you the last one 4 Has run!
# However, try to avoid simply try-excepting every
# error, as demonstrated below. This will not let you
# know what went wrong and corrupt your code.
try:
100 / 0
except:
# Not specifying anything here may result in errors at
# another place in your code --> DO NOT DO THIS
print("Some error has happend, but we do not handle it")
Some error has happend, but we do not handle it
# Typical application: Better error messages
try:
number[4]
except IndexError:
# The raised IndexError lets you put a better message into it
# for other to better understand what has went wrong
raise IndexError("You may have indexed the wrong thing. You may wanna do XYZ")
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-14-d959561b5ed6> in <cell line: 2>() 2 try: ----> 3 number[4] 4 except IndexError: IndexError: list index out of range During handling of the above exception, another exception occurred: IndexError Traceback (most recent call last) <ipython-input-14-d959561b5ed6> in <cell line: 2>() 3 number[4] 4 except IndexError: ----> 5 raise IndexError("You may have indexed the wrong thing. You may wanna do XYZ") IndexError: You may have indexed the wrong thing. You may wanna do XYZ
Classes in Python¶
In the following, I will show you HOW the process of instantiation of a class works. The code is just for demo and thus please ignore all the utterly technical terms I have 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
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}"
)
"""
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.
"""
# Regard the "class" definition above as a blueprint from which we can create many
# different "outcomes" as we want. These will differ in the VALUES of ATTRIBUTES,
# but not in the NAME of the ATTRIBUTES.
for _ in range(2):
instance = Demo()
print("ID of the instance: ", id(instance))
# 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
Instance 0 of the class has been created! We are now in the __init__ method of instance 0 ID of the instance: 132515875493280 Instance 1 of the class has been created! We are now in the __init__ method of instance 1 ID of the instance: 132515875499376
# 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 ⚙️