{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Object Oriented Programming (OOP)\n", "\"Open\n", "\n", "\n", "http://openbookproject.net/thinkcs/python/english3e/classes_and_objects_I.html\n", "http://openbookproject.net/thinkcs/python/english3e/classes_and_objects_II.html\n", "\n", "- we've been using procedural programming paradigm; focus on functions/procedures\n", "- OOP paradigm is best used in large and complex modern software systems \n", " - OOD (Object Oriented Design) makes it easy to maintain and improve software over time\n", "- focus is on creation of objects which contain both data and functionality together under one name\n", "- typically, each class definition corresponds to some object or concept in the real world with some attributes/properties that maintain its state; and the functions/methods correspond to the ways real-world objects interact" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## class\n", "- we've used classes like str, int, float, dict, tuple, etc.\n", "- class keyword lets programmer define their own compound data types\n", "- class is a collection of relevant attributes and methods like real world objects\n", "- syntax:\n", "```\n", "class className:\n", " [statement-1]\n", " .\n", " .\n", " [statement-N]\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### a simple Point class\n", "- a class that represents a point in 2-D coordinates" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# OK but NOT best practice!\n", "class Point:\n", " pass" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0\n" ] } ], "source": [ "# instantiate an object a of type Point\n", "a = Point()\n", "a.x = 0\n", "a.y = 0\n", "print(a.x, a.y)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### better class example" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " \"\"\"\n", " Point class to represent and manipulate x and y in 2D coordinates\n", " \"\"\"\n", " count = 0 # class variable/attribute\n", " \n", " # constructor to customize the initial state of an object\n", " # first argument refers to the instance being manipulated;\n", " # it is customary to name this parameter self; but can be anything\n", " def __init__(self, xx=0, yy=0):\n", " \"\"\"Create a new point with given x and y coords\"\"\"\n", " # x and y are object variables/attributes\n", " self.x = xx\n", " self.y = yy\n", " Point.count += 1 # increment class variable\n", " \n", " # destructor gets called \n", " def __del__(self):\n", " Point.count -= 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## class members\n", "- like real world objects, object instances can have both attributes and methods\n", " - attributes are properties that store data/values\n", " - methods are operations that operate on or use data/values\n", "- use . dot notation to access members\n", "- x and y are attributes of Point class\n", "- \\__init\\__() (constructor) and \\__del\\__() (destructor) are sepcial methods\n", " - more on speical methods later\n", "- can have as many relevant attributes and methods that help mimic real-world objects" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "p: x = 0 and y = 0\n", "Total point objects = 2\n", "p1: x = 10 and y = 100\n", "Total point objects = 1\n" ] } ], "source": [ "# instantiate an object\n", "p = Point()\n", "# what is the access specifier for attributes?\n", "print('p: x = {} and y = {}'.format(p.x, p.y))\n", "print(\"Total point objects = {}\".format(Point.count)) # access class variable outside class\n", "# p.__del__() # call destructor explictly\n", "p1 = Point(10, 100)\n", "print(\"p1: x = {} and y = {}\".format(p1.x, p1.y))\n", "print(\"Total point objects = {}\".format(Point.count))\n", "\n", "# Run this cell few times and see the value of Point.count\n", "# How do you fix this problem? Use __del__ destructor method.\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total point objects = 2\n" ] } ], "source": [ "print(\"Total point objects = {}\".format(Point.count))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### visualizing class and instance attributes using pythontutor.com\n", "- https://goo.gl/aGuc4r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### exercise: add a method dist_from_origin() to Point class\n", "- computes and returns the distance from the origin\n", "- test the methods" ] }, { "cell_type": "code", "execution_count": 56, "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", " " ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2.8284271247461903\n" ] } ], "source": [ "p1 = Point(2, 2)\n", "print(p1.dist_from_origin())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## objects are mutable\n", "- can change the state or attributes of an object" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(3, 2)\n", "(4, 10)\n" ] } ], "source": [ "p2 = Point(3, 2)\n", "print(p2)\n", "p2.x = 4\n", "p2.y = 10\n", "print(p2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### better approach to change state/attribute is via methods\n", "- move(xx, yy) method is added to class to set new x and y values for a point objects" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " \"\"\"\n", " Point class represents and manipulates x and y coordinates\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", " # use setters to set attributes\n", " def setX(self, xx):\n", " if isinstance(x, int) or isinstance(x, float):\n", " self.x = int(xx)\n", " elif isinstance(xx, str):\n", " if xx.isnumeric():\n", " self.x = int(xx)\n", " \n", " def setY(self, yy):\n", " if isinstance(x, int) or isinstance(x, float):\n", " self.y = int(y)\n", " elif isinstance(yy, str):\n", " if yy.isnumeric():\n", " self.y = int(yy)\n", " \n", " # use getters to get attributes\n", " def getX(self):\n", " return self.x\n", " \n", " def getY(self):\n", " return self.y\n", " \n", " def move(self, xx, yy):\n", " self.x = xx\n", " self.y = yy\n", " " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(0, 0)\n", "(10, 20)\n" ] } ], "source": [ "p3 = Point()\n", "print(p3)\n", "p3.move(10, 20)\n", "print(p3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## sameness - alias or deep copy" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "import copy\n", "p2 = Point(3, 4)\n", "p3 = p2 # alias or deepcopy?\n", "print(p2 is p3) # checks if two references refer to the same object\n", "p4 = copy.deepcopy(p2)\n", "print(p2 is p4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## passing objects as arguments to functions" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def print_point(pt):\n", " #pt.x = 100\n", " #pt.y = 100\n", " print('({0}, {1})'.format(pt.getX(), pt.getY()))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(10, 10)\n", "10 10\n" ] } ], "source": [ "p = Point(10, 10)\n", "print_point(p)\n", "#print(p)\n", "print(p.getX(), p.getY())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## are objects passed by value or reference?\n", "- how can you tell?\n", "- write a simple program to test." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## returning object instances from functions\n", "- object(s) can be returned from functions" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def midpoint(p1, p2):\n", " \"\"\"Returns the midpoint of points p1 and p2\"\"\"\n", " mx = (p1.getX() + p2.getY())/2\n", " my = (p2.getX() + p2.getY())/2\n", " return Point(mx, my)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(4.0, 5.0)\n", "(4.0, 5.0)\n" ] } ], "source": [ "p = Point(4, 6)\n", "q = Point(6, 4)\n", "r = midpoint(p, q)\n", "print_point(r) # better way to do this: use __str__() special method\n", "print(r)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "exercise 1: In-class demo: Design a class to represent a triangle and implement methods to calculate area and perimeter.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## composition\n", "- class can include another class as a member\n", "- let's say we want to represent a rectangle in a 2-D coordinates (XY plane)\n", "- corner represents the top left point on a XY plane" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "box: ((0, 0), 100, 200)\n", "bomb: ((100, 80), 5, 10)\n" ] } ], "source": [ "class Rectangle:\n", " \"\"\" A class to manufacture rectangle objects \"\"\"\n", "\n", " def __init__(self, posn, w, h):\n", " \"\"\" Initialize rectangle at posn, with width w, height h \"\"\"\n", " self.corner = posn\n", " self.width = w\n", " self.height = h\n", "\n", " def __str__(self):\n", " return \"({0}, {1}, {2})\".format(self.corner, self.width, self.height)\n", "\n", "box = Rectangle(Point(0, 0), 100, 200)\n", "bomb = Rectangle(Point(100, 80), 5, 10) # In my video game\n", "print(\"box: \", box)\n", "print(\"bomb: \", bomb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## copying objects\n", "- can be challenging as assigning one object to anohter simply creates alias" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "r1 = Rectangle(Point(1, 1), 10, 5)\n", "r2 = copy.copy(r1)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# r1 is not r2\n", "r1 is r2" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# but two corners are same\n", "r1.corner is r2.corner" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "# let's test alias by moving r1 to a different location\n", "r1.corner.move(10, 10)" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "((10, 10), 10, 5)\n", "((10, 10), 10, 5)\n" ] } ], "source": [ "# you can see r2 is moved to that location as well\n", "print(r1)\n", "print(r2)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "# fix: use deepcopy from copy module\n", "r3 = copy.deepcopy(r1)" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r1 is r3" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "((10, 10), 10, 5) ((10, 10), 10, 5)\n" ] } ], "source": [ "print(r1, r3)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "((20, 20), 10, 5) ((10, 10), 10, 5)\n" ] } ], "source": [ "r1.corner.move(20, 20)\n", "# r1 is moved but not r3\n", "print(r1, r3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }