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