====== Computer Lab 11, objects and classes II ====== This week we will work with classes representing: * 2D points: **class Point** (with attributes //x// (int), //y// (int) being coordinates of the point) * Rectangles: **class Rectangle** (with attributes //corner// (type Point), //width// (int), height (int)) class Point: """Representation of a 2D point.""" def __init__(self, x, y): """Initializes the Point object. Attributes: x (float): x coordinate of the point. y (float): y coordinate of the point. """ self.x = x self.y = y def move(self, dx, dy): return Point(self.x + dx, self.y + dy) def __str__(self): return "Point(x={}, y={})".format(self.x, self.y) def __eq__(self, other): return (self.x == other.x and self.y == other.y) class Rectangle: """Representation of a rectangle""" def __init__(self, corner, height, width): """Initializes the Rectangle object. Attributes: corner (Point): coordinates of the left upper corner of the rectangle. height (float): height of the rectangle. width (float): width of the rectangle. """ self.corner = corner self.height = height self.width = width def __str__(self): return "Rectangle({}, h={}, w={})".format(str(self.corner), self.height, self.width) def __eq__(self, other): return (self.corner == other.corner and self.height == other.height and self.width == other.width) ===Dot operator composition ==== If a function/method operating on an object is pure (returns a modified copy of the original object), it is common to use the **dot operator** multiple times. if __name__ == "__main__": p1 = Point(2, 3) print(p1) # dot operator composition p2 = p1.move(1, -2) print(p2) p3 = p1.move(1, 0).move(0, -2) print(p3) ===Objects are mutable==== Now, let's try changing the object's attributes via a (global) function: def move_point(p, dx, dy): p.x += dx p.y += dy p1 = Point(2, 3) # objects mutable print(p1) move_point(p1, 2, 3) print(p1) ===Object equality and identity==== There are several ways to **copy** an object, each of which copies an object in a slight difference sense: - The ''='' operator: Makes variables to point to the same object in memory - The ''copy.copy()'' function: Shallow copy (1 level deep). A //shallow copy// constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original. - The ''copy.deepcopy()'' function: Deep copy (recursive). A //deep copy// constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original. There are also 2 ways of **comparing** 2 objects: - ''is'' and ''is not'' checks for **identity**: Everything in Python is an object, and each object is stored at a specific memory location. The Python is and is not operators check whether two variables refer to the same object in memory. //Note//: Keep in mind that objects with the same value are usually stored at separate memory addresses. * ''=='' and ''!='' checks for **equality**: Checks whether or not two objects have the same value, regardless of where they’re stored in memory. In the vast majority of cases, this is what you want to do. The magic of the equality operator ''=='' happens in the ''%%__eq__()%%'' class method of the object to the left of the ''=='' sign. This is a magic class method that’s called whenever an instance of this class is compared against another object. If this method is not implemented, then ''=='' compares the memory addresses of the two objects by default (also see [[https://realpython.com/python-is-identity-vs-equality/|here]]). Check our implementation of ''%%__eq__()%%'' methods of the Point and Rectangle classes. **1. Copy using the "=" operator. The 2 variables pointing to the exact same object in memory.** if __name__ == "__main__": r1 = Rectangle(Point(2, 3), 10, 8) print("r1:", r1) # copy using the "=" operator # only reference is copied # difference variables pointing to the exact same object r2 = r1 print("r1 == r2: ", r1 == r2) print("r1 is r2: ", r1 is r2) print("\nr2.height = 5") r2.height = 5 print("r1:", r1) print("r2:", r2) print("r1 == r2: ", r1 == r2) print("r1 is r2: ", r1 is r2) print("\nr2.corner.x = 0") r2.corner.x = 0 print("r1:", r1) print("r2:", r2) print("r1 == r2: ", r1 == r2) print("r1 is r2: ", r1 is r2) r1: Rectangle(Point(x=2, y=3), h=10, w=8) r1 == r2: True r1 is r2: True r2.height = 5 r1: Rectangle(Point(x=2, y=3), h=5, w=8) r2: Rectangle(Point(x=2, y=3), h=5, w=8) r1 == r2: True r1 is r2: True r2.corner.x = 0 r1: Rectangle(Point(x=0, y=3), h=5, w=8) r2: Rectangle(Point(x=0, y=3), h=5, w=8) r1 == r2: True r1 is r2: True {{:courses:be5b33prg:labs:copy_slide1.png?direct|}} **2. Copy using the copy.copy() fuction (shallow copy). Separate copies up to the 1st level. ** if __name__ == "__main__": r1 = Rectangle(Point(2, 3), 10, 8) print("r1:", r1) # copy using the copy.copy() function # copy only 1 level deep # called a shallow copy r3 = copy.copy(r1) print("r1 == r3: ", r1 == r3) print("r1 is r3: ", r1 is r3) print("\nr3.height = 5") r3.height = 5 print("r1:", r1) print("r3:", r3) print("r1 == r3: ", r1 == r3) print("r1 is r3: ", r1 is r3) print("\nr3.corner.x = 0") r3.corner.x = 0 print("r1:", r1) print("r3:", r3) print("r1 == r3: ", r1 == r3) print("r1 is r3: ", r1 is r3) r1: Rectangle(Point(x=2, y=3), h=10, w=8) r1 == r3: True r1 is r3: False r3.height = 5 r1: Rectangle(Point(x=2, y=3), h=10, w=8) r3: Rectangle(Point(x=2, y=3), h=5, w=8) r1 == r3: False r1 is r3: False r3.corner.x = 0 r1: Rectangle(Point(x=0, y=3), h=10, w=8) r3: Rectangle(Point(x=0, y=3), h=5, w=8) r1 == r3: False r1 is r3: False **Shallow copy (1st level copy)** {{:courses:be5b33prg:labs:copy_slide2.png?direct|}} **1st level variable change** {{:courses:be5b33prg:labs:copy_slide3.png?direct|}} **2nd level variable change** {{:courses:be5b33prg:labs:copy_slide4.png?direct|}} **3. Copy using the copy.deepcopy() function (deep copy). Separate copies including child objects.** if __name__ == "__main__": r1 = Rectangle(Point(2, 3), 10, 8) print("r1:", r1) # copy using the copy.deepcopy() function # copy of the child objects (recursively) # called a deep copy r4 = copy.deepcopy(r1) print("r1 == r4: ", r1 == r4) print("r1 is r4: ", r1 is r4) print("\nr4.height = 5") r4.height = 5 print("r1:", r1) print("r4:", r4) print("r1 == r4: ", r1 == r4) print("r1 is r4: ", r1 is r4) print("\nr4.corner.x = 0") r4.corner.x = 0 print("r1:", r1) print("r4:", r4) print("r1 == r4: ", r1 == r4) print("r1 is r4: ", r1 is r4) r1: Rectangle(Point(x=2, y=3), h=10, w=8) r1 == r4: True r1 is r4: False r4.height = 5 r1: Rectangle(Point(x=2, y=3), h=10, w=8) r4: Rectangle(Point(x=2, y=3), h=5, w=8) r1 == r4: False r1 is r4: False r4.corner.x = 0 r1: Rectangle(Point(x=2, y=3), h=10, w=8) r4: Rectangle(Point(x=0, y=3), h=5, w=8) r1 == r4: False r1 is r4: False **Deep copy** {{:courses:be5b33prg:labs:copy_slide5.png?direct|}} ===Operator overloading==== //Motivation//: The ''+'' operator will perform arithmetic addition on two numbers, merge two lists, or concatenate two strings. How should it work with your class? * ''%%__str__(self)%%'': Controls how the object gets printed. Also, this same method is invoked when we use the built-in function ''str()'' or ''format()''. * ''%%__eq__(self, other)%%'': Equality operator. * ''%%__add__(self, other)%%'': The addition operation ''+'' is carried out the way we specify in this special method. * ''%%__lt__(self, other)%%'': Implementation of the less than symbol ''<'' symbol in our class. * Check all the arithmetic and comparison operators [[https://www.programiz.com/python-programming/operator-overloading|here]]. class Point: """Representation of a 2D point.""" def __init__(self, x, y): """Initializes the Point object. Attributes: x (float): x coordinate of the point. y (float): y coordinate of the point. """ self.x = x self.y = y def __add__(self, other): return Point(self.x + other.x, self.y + other.y) def __lt__(self, other): norm_self_quadr = self.x ** 2 + self.y ** 2 norm_other_quadr = other.x ** 2 + other.y ** 2 return norm_self_quadr < norm_other_quadr === Weekly Homework 11 - Point object === Create a class representing a 2D point in polar coordinates. Each such point is characterized by an ordered pair $(r, \phi)$: * $r$ - //radial distance//, distance from the origin to the point. * $\phi$ - //angular distance//, the angle between the positive x-axis and the line segment between the origin and the point. {{:courses:be5b33prg:labs:polar-coordinates-r-phi.png?direct&200|}} Your class must have the following structure. Copy-paste the following code and implement the (special) methods: Note: * Two points are equal if and only if both of their coordinates are the same. * Assume: $p_1=(r_1, \phi_1)$, $p_2=(r_2, \phi_2)$ * Multiplication: $p_1 \cdot p_2 = (r_1 r_2, \phi_1 + \phi_2)$ * Division: $\frac{p_1}{p_2} = (\frac{r_1}{r_2}, \phi_1 - \phi_2)$ * The functions ''%%__mul__(self, other)%%'' and ''%%__truediv__(self, other)%%'' return an instance of the PointPolar class leaving both original points unchanged. * Compare (''<'', ''<='', ''>'', ''>='') two points by their radial distances only. * In fact, it is possible for the angular distance to be over $2 \pi$ rad or less than $0$ rad, for example as a result of some operation. Please, assume that this won't happen with the supplied test cases in BRUTE. For the sake of simplicity, all input points and results of all operations will be guaranteed to have $\phi \in \left[ 0, 2 \pi \right]$ rad. Required filename: ''11_weekly_hw.py''. class PointPolar: """represents a point in polar coordinates""" def __init__(self, r, phi): """ :param r: float, norm of the point :param phi: float, angular coordinate """ def __str__(self): """ >>> p1 = PointPolar(5.1, 0.2) >>> print(p1) Point: r=5.1, phi=0.2 """ def __eq__(self, other): """Equality given by both parameters""" def __mul__(self, other): """Multiplication of 2 points""" def __truediv__(self, other): """Division of 2 points""" def __lt__(self, other): """Is self < other?""" def __le__(self, other): """Is self <= other?""" def __gt__(self, other): """Is self > other?""" def __ge__(self, other): """Is self >= other?""" Solve homework [[courses:be5b33prg:homework:regular:06_prisoners_dilemma:start|06 - Prisoner's dilemma]] and submit it via Upload system in time! Check the deadline in Upload system. The assignment will be explained more in detail during this week's lecture.