{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Object Oriented Programming (OOP)\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 which make it easy to maintain and improve 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", "- e.g., a clas that represents a point in 2-D coordinates\n", "\n", "
\n",
    "class className:\n",
    "    [statement-1]\n",
    "    .\n",
    "    .\n",
    "    [statement-N]\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### a simple Point class example" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0\n" ] } ], "source": [ "class Point:\n", " pass\n", "a = Point()\n", "a.x = 0\n", "a.y = 0\n", "print(a.x, a.y)\n", "# OK but NOT best practice!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### better class example" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "class Point:\n", " \"\"\"\n", " Point class to represent and manipulate x, y coords\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", " 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", "- 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", "- can have as many relevant attributes and methods that help mimic real-world objects" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 0\n", "1\n", "2 3\n", "2\n" ] } ], "source": [ "# instantiate an object\n", "p = Point()\n", "# what is the access specifier for attributes?\n", "print(p.x, p.y) \n", "print(Point.count) # access class variable outside class\n", "p1 = Point(2, 3)\n", "print(p1.x, p1.y)\n", "print(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": "markdown", "metadata": {}, "source": [ "### visualizing class and instance attributes using pythontutor.com\n", "- https://goo.gl/aGuc4r" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import IFrame\n", "src = \"\"\"http://pythontutor.com/iframe-embed.html#code=class%20Point%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Point%20class%20represents%20and%20manipulates%20x,y%20coords%0A%20%20%20%20%22%22%22%0A%20%20%20%20count%20%3D%200%20%23%20class%20variable/attribute%0A%20%20%20%20%0A%20%20%20%20%23%20constructor%20to%20customize%20the%20initial%20state%20of%20an%20object%0A%20%20%20%20%23%20first%20argument%20refers%20to%20the%20instance%20being%20manipulated%3B%0A%20%20%20%20%23%20it%20is%20customary%20to%20name%20this%20parameter%20self%3B%20but%20can%20be%20anything%0A%20%20%20%20def%20__init__%28self,%20xx%3D0,%20yy%3D0%29%3A%0A%20%20%20%20%20%20%20%20%22%22%22Create%20a%20new%20point%20with%20given%20x%20and%20y%20coords%22%22%22%0A%20%20%20%20%20%20%20%20%23%20x%20and%20y%20are%20object%20variables/attributes%0A%20%20%20%20%20%20%20%20self.x%20%3D%20xx%0A%20%20%20%20%20%20%20%20self.y%20%3D%20yy%0A%20%20%20%20%20%20%20%20Point.count%20%2B%3D%201%20%23%20increment%20class%20variable%0A%20%20%20%20%20%20%20%20%0Ap%20%3D%20Point%28%29%0Aprint%28p.x,%20p.y%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=false&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false\"\"\"\n", "IFrame(src, width=900, height=600)" ] }, { "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": 39, "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" ] }, { "cell_type": "code", "execution_count": 40, "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": 41, "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": 42, "metadata": {}, "outputs": [], "source": [ "def print_point(pt):\n", " #pt.x = 100\n", " #pt.y = 100\n", " print('({0}, {1})'.format(pt.x, pt.y))" ] }, { "cell_type": "code", "execution_count": 43, "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.x, p.y)" ] }, { "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" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "def midpoint(p1, p2):\n", " \"\"\"Returns the midpoint of points p1 and p2\"\"\"\n", " mx = (p1.x + p2.x)/2\n", " my = (p2.x + p2.y)/2\n", " return Point(mx, my)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(5.0, 5.0)\n", "(5.0, 5.0)\n" ] } ], "source": [ "p = Point(4, 6)\n", "q = Point(6, 4)\n", "r = midpoint(p, q)\n", "print_point(r) # fix this\n", "print(r)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "exercise 1: design a class to represent a triangle and implement methods to calculate area and perimeter." ] }, { "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 but two corners are same\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": [ "r1.corner is r2.corner" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "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": [ "# both r1 and r2 moved\n", "print(r1)\n", "print(r2)" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "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.6.2" } }, "nbformat": 4, "nbformat_minor": 2 }