{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# COMP 364: A brief interlude on inheritance\n", "\n", "Recall that we can understand the world as a hierarchy of types.\n", "\n", "![](http://www.blueoakmountaintech.com/DLF_Book.html/images/DLF_Chapter_515.gif)\n", "\n", "The same can be done with Python types:\n", "\n", "\n", "![](https://i.stack.imgur.com/33Zt8.png)\n", "\n", "A very important feature of object-oriented programming languages is **inheritance.**\n", "\n", "A derived (aka child) class is a \"specialization\" of its base (aka parent) class.\n", "\n", "\n", "Child classes (subclasses) inherit all attributes of the parent class (base class).\n", "\n", "In order to distinguish themselves, child classes can define their own versions of inherited attributes \"override\",\n", "or define their own additional attributes.\n", "\n", "We already saw how to make our own class.\n", "\n", "When we don't specify any parent class, the parent class is `object` by default." ] }, { "cell_type": "code", "execution_count": 148, "metadata": {}, "outputs": [], "source": [ "#inherits all its atributes from `object` class\n", "class MyClass:\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use the built-in function `issubclass(first_class, second_class)` to see if a class is a subclass of another." ] }, { "cell_type": "code", "execution_count": 151, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 151, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#let's create an instance of the type MyClass\n", "issubclass(MyClass, object)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 153, "metadata": { "collapsed": true }, "outputs": [], "source": [ "#calls the __init__() method from `object` class\n", "m = MyClass()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another useful built-in function is `isinstance(object, class)` which tells us if an object is an instance of a given class." ] }, { "cell_type": "code", "execution_count": 154, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 154, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(m, MyClass)" ] }, { "cell_type": "code", "execution_count": 155, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 155, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(m, object)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The useful thing with inheritance is that it lets us create specialized types without having to re-define every attribute and method each time.\n", "\n", "The syntax for creating a subclass from a base class is as follows:\n", "\n", "```python\n", "class Parent:\n", " pass\n", "class Child(Parent):\n", " pass\n", "```\n", "\n", "When creating a child (subclass) class, you specify in round brackets, the name of the parent class.\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note:" ] }, { "cell_type": "code", "execution_count": 156, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class MyClass:\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Is equivalent to" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class MyClass(object):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's make a class that describes Animals in general.\n", "\n", "Just for clarity we're explicitly inheriting from `object`." ] }, { "cell_type": "code", "execution_count": 157, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Animal(object):\n", " def __init__(self, n):\n", " self.num_legs = n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can create instances of the Animal class." ] }, { "cell_type": "code", "execution_count": 158, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a = Animal(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When Python `print()` is called on on object, it calls that object's `__str__` method." ] }, { "cell_type": "code", "execution_count": 159, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Animal object at 0x10889a588>\n" ] } ], "source": [ "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We want a prettier print message than the parent's, so let's override it." ] }, { "cell_type": "code", "execution_count": 160, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Animal(object):\n", " def __init__(self, n, e):\n", " self.num_legs = n\n", " self.food = e\n", " def __str__(self):\n", " return f\"This animal has {self.num_legs} legs and is a {self.food} eater.\"" ] }, { "cell_type": "code", "execution_count": 161, "metadata": {}, "outputs": [], "source": [ "a = Animal(4, \"meat\")\n" ] }, { "cell_type": "code", "execution_count": 162, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This animal has 4 legs and is a meat eater.\n" ] } ], "source": [ "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "Now let's specialize the `Animal` class with `Dog`.\n", "\n", "Dogs should also have `num_legs` and `food` attributes so let's initialize them using the parent's `__init__`." ] }, { "cell_type": "code", "execution_count": 171, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Dog(Animal):\n", " def __init__(self, b):\n", " Animal.__init__(self, 4, \"meat\")\n", " self.breed = b" ] }, { "cell_type": "code", "execution_count": 172, "metadata": { "collapsed": true }, "outputs": [], "source": [ "pyr = Dog(\"Great Pyrenees\")" ] }, { "cell_type": "code", "execution_count": 173, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This animal has 4 legs and is a meat eater.\n" ] } ], "source": [ "print(pyr)" ] }, { "cell_type": "code", "execution_count": 174, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Great Pyrenees\n" ] } ], "source": [ "print(pyr.breed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Child classes can define their own methods." ] }, { "cell_type": "code", "execution_count": 175, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Dog(Animal):\n", " def __init__(self, b):\n", " \"\"\"\n", " call the parent init function.\n", " equivalent to:\n", " self.num_legs = 4\n", " self.food = \"meat\" \n", " \"\"\"\n", "\n", " Animal.__init__(self, 4, \"meat\")\n", " self.breed = b\n", " def bark(self):\n", " if self.breed == \"Great Pyrenees\":\n", " print(\"WOOF \" * 10)\n", " else:\n", " print(\"woof\")" ] }, { "cell_type": "code", "execution_count": 176, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF \n" ] } ], "source": [ "p = Dog(\"Great Pyrenees\")\n", "p.bark()" ] }, { "cell_type": "code", "execution_count": 177, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "woof\n" ] } ], "source": [ "d = Dog(\"Golden Retriever\")\n", "d.bark()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Objects of the parent class won't have a `bark()` method." ] }, { "cell_type": "code", "execution_count": 178, "metadata": {}, "outputs": [ { "ename": "AttributeError", "evalue": "'Animal' object has no attribute 'bark'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mAnimal\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"meat\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbark\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m: 'Animal' object has no attribute 'bark'" ] } ], "source": [ "a = Animal(4, \"meat\")\n", "\n", "a.bark()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's say we want a better print message than the generic `Animal` one." ] }, { "cell_type": "code", "execution_count": 179, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Dog(Animal):\n", " def __init__(self, b):\n", " \"\"\"\n", " call the parent init function.\n", " equivalent to:\n", " self.num_legs = 4\n", " self.food = \"meat\" \n", " \"\"\"\n", "\n", " Animal.__init__(self, 4, \"meat\")\n", " self.breed = b\n", " def bark(self):\n", " if self.breed == \"Great Pyrenees\":\n", " print(\"WOOF \" * 10)\n", " else:\n", " print(\"woof\")\n", " def __str__(self):\n", " return Animal.__str__(self) + f\" And it is a {self.breed} dog.\"" ] }, { "cell_type": "code", "execution_count": 180, "metadata": { "collapsed": true }, "outputs": [], "source": [ "p = Dog(\"Great Pyrenees\")" ] }, { "cell_type": "code", "execution_count": 181, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This animal has 4 legs and is a meat eater. And it is a Great Pyrenees dog.\n" ] } ], "source": [ "print(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can inherit from `Animal` again to define a new kind of animal." ] }, { "cell_type": "code", "execution_count": 182, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Cat(Animal):\n", " def __init__(self, c):\n", " Animal.__init__(self, 4, \"mice\")\n", " self.color = c\n", " " ] }, { "cell_type": "code", "execution_count": 183, "metadata": { "collapsed": true }, "outputs": [], "source": [ "c = Cat(\"Black\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have something like this:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " +-----------+\n", " | |\n", " | object |\n", " +-----------+\n", " ^\n", " |\n", " |\n", " +-----+-----+\n", " | Animal |\n", " | |\n", " +-----------+\n", " ^ ^\n", " | |\n", " +----------+ +------------+\n", " | |\n", " +------+----+ +----+------+\n", " | Dog | | Cat |\n", " | | | |\n", " +-----------+ +-----------+" ] }, { "cell_type": "code", "execution_count": 184, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class SpecificDog(Dog):\n", " def __init__(self, name, breed):\n", " Dog.__init__(self, breed)\n", " self.name = name" ] }, { "cell_type": "code", "execution_count": 185, "metadata": { "collapsed": true }, "outputs": [], "source": [ "mydog = SpecificDog(\"Sasja\", \"Great Pyrenees\")" ] }, { "cell_type": "code", "execution_count": 186, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Sasja'" ] }, "execution_count": 186, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mydog.name" ] }, { "cell_type": "code", "execution_count": 187, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF WOOF \n" ] } ], "source": [ "mydog.bark()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " +-----------+\n", " | |\n", " | object |\n", " +-----------+\n", " ^\n", " |\n", " |\n", " +-----+-----+\n", " | Animal |\n", " | |\n", " +-----------+\n", " ^ ^\n", " | |\n", " +----------+ +------------+\n", " | |\n", " +------+----+ +----+------+\n", " | Dog | | Cat |\n", " | | | |\n", " +-----------+ +-----------+\n", " ^\n", " |\n", " | \n", " +-----+-----+\n", " |SpecificDog|\n", " | |\n", " +-----------+" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "* Classes/types define the attributes of objects.\n", "* Every object belongs to a class/type\n", "* Classes can inherit attributes from parent (base) classes.\n", "* Derived classes have all their parents attributes except:\n", " * New attributes defined in the derived classs\n", " * Attributes that were overridden in the derived class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I will define below all the clases we defined above but without using inheritance (other than default inheritance from `object`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Animal:\n", " def __init__(self, n, f):\n", " self.num_legs = n\n", " self.food = f\n", " def __str__(self):\n", " return f\"This animal has {self.num_legs} legs and is a {self.food} eater.\"\n", "class Dog:\n", " def __init__(self, b):\n", " self.num_legs = 4\n", " self.food = \"meat\"\n", " self.breed = b\n", " def bark(self):\n", " if self.breed == \"Great Pyrenees\":\n", " print(\"WOOF \" * 10)\n", " else:\n", " print(\"woof\")\n", " def __str__(self):\n", " return f\"This animal has {self.num_legs} legs and is a {self.food} eater. And it is a {self.breed} dog.\"\n", "class Cat:\n", " def __init__(self, c):\n", " self.num_legs = 4\n", " self.food = \"mice\"\n", " self.breed = b\n", "class SpecificDog:\n", " def __init__(self, n, b):\n", " self.num_legs = 4\n", " self.food = \"meat\"\n", " self.breed = b\n", " self.name = n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lots of repetitive code!\n", "\n", "versus:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Animal(object):\n", " def __init__(self, n, e):\n", " self.num_legs = n\n", " self.food = e\n", " def __str__(self):\n", " return f\"This animal has {self.num_legs} legs and is a {self.food} eater.\"\n", " \n", "class Dog(Animal):\n", " def __init__(self, b):\n", " Animal.__init__(self, 4, \"meat\")\n", " self.breed = b\n", " def bark(self):\n", " if self.breed == \"Great Pyrenees\":\n", " print(\"WOOF \" * 10)\n", " else:\n", " print(\"woof\")\n", " def __str__(self):\n", " return Animal.__str__(self) + f\" And it is a {self.breed} dog.\"\n", "\n", "class Cat(Animal):\n", " def __init__(self, c):\n", " Animal.__init__(self, 4, \"mice\")\n", " self.color = c\n", " \n", "class SpecificDog(Dog):\n", " def __init__(self, name, breed):\n", " Dog.__init__(self, breed)\n", " self.name = name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you can customize any class and add your own functionality to it.\n", "\n", "Note that the name of the class you are extending has to be accessible from the current namespace.\n", "\n", "For example, if we wanted to specialize the `Seq` object we have to import it into our namespace." ] }, { "cell_type": "code", "execution_count": 188, "metadata": {}, "outputs": [], "source": [ "from Bio.Seq import Seq" ] }, { "cell_type": "code", "execution_count": 189, "metadata": {}, "outputs": [], "source": [ "class MySeq(Seq):\n", " def __init__(self, seq):\n", " Seq.__init__(self, seq)\n", " self.myattribute = \"HELLO\"\n", " #Seq doesn't compute GC content so I'll add that functionality\n", " def compute_gc(self):\n", " return len([b for b in self if b in [\"G\", \"C\"]]) / len(self)" ] }, { "cell_type": "code", "execution_count": 190, "metadata": {}, "outputs": [], "source": [ "s = MySeq(\"AAATTCGAGAG\")" ] }, { "cell_type": "code", "execution_count": 191, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "AAAUUCGAGAG\n" ] } ], "source": [ "print(s.transcribe())" ] }, { "cell_type": "code", "execution_count": 192, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HELLO\n" ] } ], "source": [ "print(s.myattribute)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I added my own attributes without having to re-write the whole `Seq` class." ] }, { "cell_type": "code", "execution_count": 193, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.36363636363636365" ] }, "execution_count": 193, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.compute_gc()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "class Student:\n", " def __init__(self, name, gpa):\n", " self.name = name\n", " self.gpa = gpa\n", "class Course:\n", " def __init__(self, students):\n", " self.students = studnet" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "students = []\n", "with open(\"students.txt\", \"r\") as s:\n", " for line in s:\n", " students.append(Student(line[0], line[1]))\n", "for s in students:\n", " if s.grade > 2:\n", " print(s.name)\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.2" } }, "nbformat": 4, "nbformat_minor": 2 }