Recall that we can understand the world as a hierarchy of types.
The same can be done with Python types:
A very important feature of object-oriented programming languages is inheritance.
A derived (aka child) class is a "specialization" of its base (aka parent) class.
Child classes (subclasses) inherit all attributes of the parent class (base class).
In order to distinguish themselves, child classes can define their own versions of inherited attributes "override", or define their own additional attributes.
We already saw how to make our own class.
When we don't specify any parent class, the parent class is object
by default.
#inherits all its atributes from `object` class
class MyClass:
pass
We can use the built-in function issubclass(first_class, second_class)
to see if a class is a subclass of another.
#let's create an instance of the type MyClass
issubclass(MyClass, object)
True
Since MyClass
doesn't define any of its own new attributes or override any of object
's attributes it have exactly all of object
's attributes.
#calls the __init__() method from `object` class
m = MyClass()
Another useful built-in function is isinstance(object, class)
which tells us if an object is an instance of a given class.
isinstance(m, MyClass)
True
isinstance(m, object)
True
The useful thing with inheritance is that it lets us create specialized types without having to re-define every attribute and method each time.
The syntax for creating a subclass from a base class is as follows:
class Parent:
pass
class Child(Parent):
pass
When creating a child (subclass) class, you specify in round brackets, the name of the parent class.
This tells Python to look in the parent class for any attributes of the Child class that get called it they are not defined in the child class.
Note:
class MyClass:
pass
Is equivalent to
class MyClass(object):
pass
Let's make a class that describes Animals in general.
Just for clarity we're explicitly inheriting from object
.
class Animal(object):
def __init__(self, n):
self.num_legs = n
Now we can create instances of the Animal class.
a = Animal(4)
When Python print()
is called on on object, it calls that object's __str__
method.
print(a)
<__main__.Animal object at 0x10889a588>
We want a prettier print message than the parent's, so let's override it.
class Animal(object):
def __init__(self, n, e):
self.num_legs = n
self.food = e
def __str__(self):
return f"This animal has {self.num_legs} legs and is a {self.food} eater."
a = Animal(4, "meat")
print(a)
This animal has 4 legs and is a meat eater.
Python looks first in the child class for __str__
because it found a definition it stops looking and calls it instead of the parent's.
Now let's specialize the Animal
class with Dog
.
Dogs should also have num_legs
and food
attributes so let's initialize them using the parent's __init__
.
class Dog(Animal):
def __init__(self, b):
Animal.__init__(self, 4, "meat")
self.breed = b
pyr = Dog("Great Pyrenees")
print(pyr)
This animal has 4 legs and is a meat eater.
print(pyr.breed)
Great Pyrenees
Child classes can define their own methods.
class Dog(Animal):
def __init__(self, b):
"""
call the parent init function.
equivalent to:
self.num_legs = 4
self.food = "meat"
"""
Animal.__init__(self, 4, "meat")
self.breed = b
def bark(self):
if self.breed == "Great Pyrenees":
print("WOOF " * 10)
else:
print("woof")
p = Dog("Great Pyrenees")
p.bark()
WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF
d = Dog("Golden Retriever")
d.bark()
woof
Objects of the parent class won't have a bark()
method.
a = Animal(4, "meat")
a.bark()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-178-90dc5d3b38a0> in <module>() 1 a = Animal(4, "meat") 2 ----> 3 a.bark() AttributeError: 'Animal' object has no attribute 'bark'
Let's say we want a better print message than the generic Animal
one.
class Dog(Animal):
def __init__(self, b):
"""
call the parent init function.
equivalent to:
self.num_legs = 4
self.food = "meat"
"""
Animal.__init__(self, 4, "meat")
self.breed = b
def bark(self):
if self.breed == "Great Pyrenees":
print("WOOF " * 10)
else:
print("woof")
def __str__(self):
return Animal.__str__(self) + f" And it is a {self.breed} dog."
p = Dog("Great Pyrenees")
print(p)
This animal has 4 legs and is a meat eater. And it is a Great Pyrenees dog.
We can inherit from Animal
again to define a new kind of animal.
class Cat(Animal):
def __init__(self, c):
Animal.__init__(self, 4, "mice")
self.color = c
c = Cat("Black")
Now we have something like this:
+-----------+
| |
| object |
+-----------+
^
|
|
+-----+-----+
| Animal |
| |
+-----------+
^ ^
| |
+----------+ +------------+
| |
+------+----+ +----+------+
| Dog | | Cat |
| | | |
+-----------+ +-----------+
class SpecificDog(Dog):
def __init__(self, name, breed):
Dog.__init__(self, breed)
self.name = name
mydog = SpecificDog("Sasja", "Great Pyrenees")
mydog.name
'Sasja'
mydog.bark()
WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF
+-----------+
| |
| object |
+-----------+
^
|
|
+-----+-----+
| Animal |
| |
+-----------+
^ ^
| |
+----------+ +------------+
| |
+------+----+ +----+------+
| Dog | | Cat |
| | | |
+-----------+ +-----------+
^
|
|
+-----+-----+
|SpecificDog|
| |
+-----------+
I will define below all the clases we defined above but without using inheritance (other than default inheritance from object
).
class Animal:
def __init__(self, n, f):
self.num_legs = n
self.food = f
def __str__(self):
return f"This animal has {self.num_legs} legs and is a {self.food} eater."
class Dog:
def __init__(self, b):
self.num_legs = 4
self.food = "meat"
self.breed = b
def bark(self):
if self.breed == "Great Pyrenees":
print("WOOF " * 10)
else:
print("woof")
def __str__(self):
return f"This animal has {self.num_legs} legs and is a {self.food} eater. And it is a {self.breed} dog."
class Cat:
def __init__(self, c):
self.num_legs = 4
self.food = "mice"
self.breed = b
class SpecificDog:
def __init__(self, n, b):
self.num_legs = 4
self.food = "meat"
self.breed = b
self.name = n
Lots of repetitive code!
versus:
class Animal(object):
def __init__(self, n, e):
self.num_legs = n
self.food = e
def __str__(self):
return f"This animal has {self.num_legs} legs and is a {self.food} eater."
class Dog(Animal):
def __init__(self, b):
Animal.__init__(self, 4, "meat")
self.breed = b
def bark(self):
if self.breed == "Great Pyrenees":
print("WOOF " * 10)
else:
print("woof")
def __str__(self):
return Animal.__str__(self) + f" And it is a {self.breed} dog."
class Cat(Animal):
def __init__(self, c):
Animal.__init__(self, 4, "mice")
self.color = c
class SpecificDog(Dog):
def __init__(self, name, breed):
Dog.__init__(self, breed)
self.name = name
Now you can customize any class and add your own functionality to it.
Note that the name of the class you are extending has to be accessible from the current namespace.
For example, if we wanted to specialize the Seq
object we have to import it into our namespace.
from Bio.Seq import Seq
class MySeq(Seq):
def __init__(self, seq):
Seq.__init__(self, seq)
self.myattribute = "HELLO"
#Seq doesn't compute GC content so I'll add that functionality
def compute_gc(self):
return len([b for b in self if b in ["G", "C"]]) / len(self)
s = MySeq("AAATTCGAGAG")
print(s.transcribe())
AAAUUCGAGAG
print(s.myattribute)
HELLO
I added my own attributes without having to re-write the whole Seq
class.
s.compute_gc()
0.36363636363636365
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
class Course:
def __init__(self, students):
self.students = studnet
students = []
with open("students.txt", "r") as s:
for line in s:
students.append(Student(line[0], line[1]))
for s in students:
if s.grade > 2:
print(s.name)