{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# More OOP, Operator Overloading and Polymorphism\n",
"\n",
"\n",
"\n",
"\n",
"http://openbookproject.net/thinkcs/python/english3e/even_more_oop.html\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": 3,
"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": 4,
"metadata": {},
"outputs": [],
"source": [
"tim1 = MyTime(11, 59, 3)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"11:59:03\n"
]
}
],
"source": [
"print(tim1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Functions can be pure and modifiers\n",
"- what functions should be part of class of methods?\n",
"- typically, all the rfunctions that operate on or use attributes of class should be part of the class called methods"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## pure functions\n",
"- pure functions do not have side effects, such as modifying parameters and global variables\n",
"- similar to constant functions in C++ world\n",
"- getter methods are pure functions\n",
"- e.g.: see add_time()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"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",
" while s >= 60:\n",
" s -= 60\n",
" m += 1\n",
"\n",
" while m >= 60:\n",
" m -= 60\n",
" h += 1\n",
"\n",
" sum_t = MyTime(h, m, s)\n",
" return sum_t"
]
},
{
"cell_type": "code",
"execution_count": 32,
"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)\n",
"- setter methods are modifiers"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"# function takes MyTime t and secs to update t\n",
"def increment(myT, seconds):\n",
" myT.seconds += seconds\n",
" mins = myT.seconds//60\n",
" \n",
" myT.seconds = myT.seconds%60\n",
" myT.minutes += mins\n",
" \n",
" hours = myT.minutes//60\n",
" myT.hours += hours\n",
" myT.minutes = myT.minutes%60"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"09:50:45\n"
]
}
],
"source": [
"current_time = MyTime(9, 50, 45)\n",
"print(current_time)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"increment(current_time, 60*60)"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"10:50:45\n"
]
}
],
"source": [
"print(current_time)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Converting increment() to a method\n",
"- OOD prefers that functions that work with objects to be part of the class or methods\n",
"- increment can be a useful method for MyTime class"
]
},
{
"cell_type": "code",
"execution_count": 35,
"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",
" 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, seconds):\n",
" self.seconds += seconds\n",
" self.__normalize()\n",
" \n",
" # should be treated as private method\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": 36,
"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": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"12:26:05\n"
]
}
],
"source": [
"# test add_time function\n",
"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": [
"### similarly, add_time can be moved inside MyTime class as a method"
]
},
{
"cell_type": "code",
"execution_count": 38,
"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",
" # 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": 39,
"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\n",
"- overloading our Point class to be able to 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 (int or float) \n",
" 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