{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# More OOP, Operator Overloading and Polymorphism\n", "\n", "## MyTime\n", "- class that records the time of day\n", "- provide __init__ method so every instance is created with appropriate attributes and initialization" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class MyTime:\n", " \"\"\"MyTime class that keeps track of time of day\"\"\"\n", " def __init__(self, hrs=0, mins=0, secs=0):\n", " \"\"\" Creates a MyTime object initialized to hrs, mins, secs \"\"\"\n", " self.hours = hrs\n", " self.minutes = mins\n", " self.seconds = secs\n", " \n", " def __str__(self):\n", " return \"{:02}:{:02}:{:02}\".format(self.hours, self.minutes, self.seconds)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "tim1 = MyTime(11, 59, 3)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "11:59:03\n" ] } ], "source": [ "print(tim1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## pure functions\n", "- pure functions do not have side effects, such as updating parameters and global variables, displaying a value or getting user input\n", "- e.g.: see add_time()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "# adds two times and returns a new time\n", "def add_time(t1, t2):\n", " h = t1.hours + t2.hours\n", " m = t1.minutes + t2.minutes\n", " s = t1.seconds + t2.seconds\n", " sum_t = MyTime(h, m, s)\n", " return sum_t" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12:49:30\n" ] } ], "source": [ "current_time = MyTime(9, 14, 30)\n", "bread_time = MyTime(3, 35, 0)\n", "done_time = add_time(current_time, bread_time)\n", "print(done_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### what if?" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "11:85:65\n" ] } ], "source": [ "current_time = MyTime(9, 50, 45)\n", "bread_time = MyTime(2, 35, 20)\n", "done_time = add_time(current_time, bread_time)\n", "print(done_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### not correct result or HH:MM:SS" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def add_time(t1, t2):\n", " h = t1.hours + t2.hours\n", " m = t1.minutes + t2.minutes\n", " s = t1.seconds + t2.seconds\n", "\n", " if s >= 60: # okay, but not perfect! can you see why?\n", " s -= 60\n", " m += 1\n", "\n", " if m >= 60: # okay, again!\n", " m -= 60\n", " h += 1\n", "\n", " sum_t = MyTime(h, m, s)\n", " return sum_t" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12:26:05\n" ] } ], "source": [ "current_time = MyTime(9, 50, 45)\n", "bread_time = MyTime(2, 35, 20)\n", "done_time = add_time(current_time, bread_time)\n", "print(done_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## modifiers\n", "- functions that modify the object(s) it gets as parameter(s)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "def increment(t, secs):\n", " t.seconds += secs\n", " mins = t.seconds//60\n", " \n", " t.seconds = t.seconds%60\n", " t.minutes += mins\n", " \n", " hours = t.minutes//60\n", " t.hours += hours\n", " t.minutes = t.minutes%60" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "increment(current_time, 60*60)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10:50:45\n" ] } ], "source": [ "print(current_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## an \"aha!\" insight" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [], "source": [ "class MyTime:\n", " def __init__(self, hrs=0, mins=0, secs=0):\n", " \"\"\" Create a new MyTime object initialized to hrs, mins, secs.\n", " The values of mins and secs may be outside the range 0-59,\n", " but the resulting MyTime object will be normalized.\n", " \"\"\"\n", " self.hours = hrs\n", " self.minutes = mins\n", " self.seconds = secs\n", " \n", " # Calculate total seconds to represent\n", " totalsecs = self.to_seconds()\n", " self.hours = totalsecs // 3600 # Split in h, m, s\n", " leftoversecs = totalsecs % 3600\n", " self.minutes = leftoversecs // 60\n", " self.seconds = leftoversecs % 60\n", "\n", " def __str__(self):\n", " return \"{:02}:{:02}:{:02}\".format(self.hours, self.minutes, self.seconds)\n", " \n", " def to_seconds(self):\n", " \"\"\" Return the number of seconds represented\n", " by this instance\n", " \"\"\"\n", " return self.hours * 3600 + self.minutes * 60 + self.seconds" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "# improved add_time function\n", "def add_time(t1, t2):\n", " secs = t1.to_seconds() + t2.to_seconds()\n", " return MyTime(0, 0, secs)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "11:21:31\n" ] } ], "source": [ "t1 = MyTime(10, 20, 30)\n", "t2 = MyTime(1, 1, 1)\n", "t3 = add_time(t1, t2)\n", "print(t3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### increment function can better serve as a method" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "class MyTime:\n", "\n", " def __init__(self, hrs=0, mins=0, secs=0):\n", " \"\"\" Create a new MyTime object initialized to hrs, mins, secs.\n", " The values of mins and secs may be outside the range 0-59,\n", " but the resulting MyTime object will be normalized.\n", " \"\"\"\n", " self.hours = hrs\n", " self.minutes = mins\n", " self.seconds = secs\n", " # Calculate total seconds to represent\n", " self.__normalize()\n", " \n", " def __str__(self):\n", " return \"{:02}:{:02}:{:02}\".format(self.hours, self.minutes, self.seconds)\n", " \n", " def to_seconds(self):\n", " \"\"\" Return the number of seconds represented\n", " by this instance\n", " \"\"\"\n", " return self.hours * 3600 + self.minutes * 60 + self.seconds\n", " \n", " def increment(self, secs):\n", " self.seconds += secs\n", " self.__normalize()\n", " \n", " def __normalize(self):\n", " totalsecs = self.to_seconds()\n", " self.hours = totalsecs // 3600 # Split in h, m, s\n", " leftoversecs = totalsecs % 3600\n", " self.minutes = leftoversecs // 60\n", " self.seconds = leftoversecs % 60" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "09:50:45\n", "10:50:45\n" ] } ], "source": [ "current_time = MyTime(9, 50, 45)\n", "print(current_time)\n", "current_time.increment(60*60)\n", "print(current_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### similarly, add_time can be moved inside MyTime class as a method" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "class MyTime:\n", "\n", " def __init__(self, hrs=0, mins=0, secs=0):\n", " \"\"\" Create a new MyTime object initialized to hrs, mins, secs.\n", " The values of mins and secs may be outside the range 0-59,\n", " but the resulting MyTime object will be normalized.\n", " \"\"\"\n", " self.hours = hrs\n", " self.minutes = mins\n", " self.seconds = secs\n", " # Calculate total seconds to represent\n", " self.__normalize()\n", " \n", " def __str__(self):\n", " return \"{:02}:{:02}:{:02}\".format(self.hours, self.minutes, self.seconds)\n", " \n", " def to_seconds(self):\n", " \"\"\" Return the number of seconds represented\n", " by this instance\n", " \"\"\"\n", " return self.hours * 3600 + self.minutes * 60 + self.seconds\n", " \n", " def increment(self, secs):\n", " self.seconds += secs\n", " self.normalize()\n", " \n", " def __normalize(self):\n", " totalsecs = self.to_seconds()\n", " self.hours = totalsecs // 3600 # Split in h, m, s\n", " leftoversecs = totalsecs % 3600\n", " self.minutes = leftoversecs // 60\n", " self.seconds = leftoversecs % 60\n", " \n", " def add_time(self, other):\n", " return MyTime(0, 0, self.to_seconds() + other.to_seconds())\n", " " ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12:26:05\n" ] } ], "source": [ "current_time = MyTime(9, 50, 45)\n", "bread_time = MyTime(2, 35, 20)\n", "done_time = current_time.add_time(bread_time)\n", "print(done_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## special methods / operator overloading\n", "- https://docs.python.org/3/reference/datamodel.html\n", "- how about t1 = t2 + t3 just like adding primitive types\n", "- \\+ operator appends two strings, but adds two integers or floats\n", "- the same operator has different meaning for different types called operator overloading\n", "- replace add_time with built-in special method __add__ to overload + operator" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": [ "class MyTime:\n", "\n", " def __init__(self, hrs=0, mins=0, secs=0):\n", " \"\"\" Create a new MyTime object initialized to hrs, mins, secs.\n", " The values of mins and secs may be outside the range 0-59,\n", " but the resulting MyTime object will be normalized.\n", " \"\"\"\n", " self.hours = hrs\n", " self.minutes = mins\n", " self.seconds = secs\n", " # Calculate total seconds to represent\n", " self.__normalize()\n", " \n", " def __str__(self):\n", " return \"{:02}:{:02}:{:02}\".format(self.hours, self.minutes, self.seconds)\n", " \n", " def to_seconds(self):\n", " \"\"\" Return the number of seconds represented\n", " by this instance\n", " \"\"\"\n", " return self.hours * 3600 + self.minutes * 60 + self.seconds\n", " \n", " def increment(self, secs):\n", " self.seconds += secs\n", " self.normalize()\n", " \n", " def __normalize(self):\n", " totalsecs = self.to_seconds()\n", " self.hours = totalsecs // 3600 # Split in h, m, s\n", " leftoversecs = totalsecs % 3600\n", " self.minutes = leftoversecs // 60\n", " self.seconds = leftoversecs % 60\n", " \n", " def __add__(self, other):\n", " return MyTime(0, 0, self.to_seconds() + other.to_seconds())" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "12:26:05\n" ] } ], "source": [ "current_time = MyTime(9, 50, 45)\n", "bread_time = MyTime(2, 35, 20)\n", "done_time = current_time + bread_time # equivalent to: done_time = current_time.__add__(bread_time)\n", "print(done_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## add two points" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " \"\"\"\n", " Point class represents and manipulates x,y coords\n", " \"\"\"\n", " count = 0\n", " \n", " def __init__(self, xx=0, yy=0):\n", " \"\"\"Create a new point with given x and y coords\"\"\"\n", " self.x = xx\n", " self.y = yy\n", " Point.count += 1\n", " \n", " def dist_from_origin(self):\n", " import math\n", " dist = math.sqrt(self.x**2+self.y**2)\n", " return dist\n", " \n", " def __str__(self):\n", " return \"({}, {})\".format(self.x, self.y)\n", " \n", " def move(self, xx, yy):\n", " self.x = xx\n", " self.y = yy\n", " \n", " def __add__(self, other):\n", " x = self.x + other.x\n", " y = self.y + other.y\n", " return Point(x, y)\n", " \n", " def __mul__(self, other):\n", " \"\"\"\n", " computes dot product of two points\n", " \"\"\"\n", " return self.x * other.x + self.y * other.y\n", " \n", " def __rmul__(self, other):\n", " \"\"\"\n", " if the left operand is primitive type and the right operand is a Point, Python invokes __rmul__\n", " which performs scalar multiplication\n", " \"\"\"\n", " return Point(other * self.x, other * self.y)\n", " " ] }, { "cell_type": "code", "execution_count": 80, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(12, 12)\n", "48\n", "(8, 8)\n" ] } ], "source": [ "p1 = Point(2, 2)\n", "p2 = Point(10, 10)\n", "p3 = p1 + p2\n", "print(p3)\n", "print(p1 * p3)\n", "print(4 * p1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## some special methods\n", "
\n", "__del__(self)\n", " - destructor - called when instance is about to be destroyed\n", " \n", "__str__(self)\n", " - called by str(object) and the built-in functions format() and print() to computer the \"informal\" or nicely printable string representation of an object.\n", " - must return string object\n", "\n", "__lt__(self, other)\n", " x < y calls x.__lt__(y)\n", "\n", "__gt__(self, other)\n", " x > y calls x.__gt__(y)\n", " \n", "__eq__(self, other)\n", " x == y calls x.__eq__(y)\n", "\n", "__ne__(self, other)\n", "__ge__(self, other) \n", "__le__(self, other)\n", "\n", "Emulating numeric types:\n", "__add__(self, other)\n", "__sub__(self, other)\n", "__mul__(self, other)\n", "__mod__(self, other)\n", "__truediv__(self, other)\n", "__pow__(self, other)\n", "__xor__(self, other)\n", "__or__(self, other)\n", "__and__(self, other)\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "exercise 1: implement some relevant special methods for Point class and test them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "exercise 2: implement some relevant special methods for Triangle class defined in previous chapter and test them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Polymorphism\n", "- most methods work on a specific new class type we create\n", "- some methods we want to apply to many types, such as arithmetic operations + in previous example\n", "- e.g., multadd operation (common in linear algebra) takes 3 arguments, it multiplies the first two and then adds the third\n", "- function like this that can take arguments with different types is called polymorphic" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [], "source": [ "def multadd(x, y, z):\n", " return x * y + z" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "multadd(3, 2, 1)" ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(11, 15)\n" ] } ], "source": [ "p1 = Point(3, 4)\n", "p2 = Point(5, 7)\n", "print(multadd(2, p1, p2))" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "44\n" ] } ], "source": [ "print(multadd (p1, p2, 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## duck typing rule - dynamic binding\n", "- duck test: \"If it walks like a duck and it quacks like a duck, then it must be a duck\" \n", "- to determine whether a function can be applied to a new type, we apply Python's fundamental rule of polymorphism, called duck typing rule: if all of the operations inside the function can be applied to the type, the function can be applied to the type \n", "- e.g.: https://en.wikipedia.org/wiki/Duck_typing" ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Duck flying\n", "Airplane flying\n" ] }, { "ename": "AttributeError", "evalue": "'Whale' object has no attribute 'fly'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m