{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lecture 11 - Classes\n", "\n", "## Object (variable) Method (function)\n", "\n", "In this lecture we will learn classes and the basics of object oriented programming. It's important to know the main idea and be able to write simple classes that serve basic mathematical purposes.\n", "\n", "This also prepares us for the `scikit-learn` class objects we will use extensively in the second half.\n", "\n", "This will explain expressions like `x.append(5)` that we used for lists and `arr.reshape(4,-1)` for numpy arrays. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Review" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "arr = np.arange(10)\n", "# arr. # press tab after the dot\n", "print(arr.reshape(2,-1))\n", "print()\n", "print(np.reshape(arr, [2, -1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main idea of classes is that sometimes you want to put various pieces of data, and functions that manipulate the data in one place. The functions are called *methods* of the class. \n", "\n", "For example, a vector in $\\mathbb{R}^3$ is represented by three floats. So why not make a new data type that contains three floats and call it a vector?\n", "\n", "Actually, let's make a list of all the things we know we can do to vectors in $\\mathbb{R}^3$. We can:\n", "\n", "* Have x, y, z variables for the three coordinates,\n", "* take the length of a vector (a.k.a., 2-norm),\n", "* we can normalize the vector, meaning we can divide it by the norm so that it now has the same direction but has length 1,\n", "* take dot product of two vectors, i.e. take $(x_1,y_1,z_1)\\cdot(x_2,y_2,z_2) = x_1x2 + y_1y_2 + z_1z_2$,\n", "* add vectors,\n", "* multiply a vector by a scalar\n", "* take cross product of two vectors\n", "\n", "We will make a new type of object in Python, called Vector, that allows us to do all of these things. \n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math \n", "# it is always a good habit to import module outside a function or a class\n", "class Vector():\n", " \n", " # initialize the vector, we will say \n", " # v = Vector(1,2,3)\n", " # to get a new vector with those coordinated\n", " def __init__(self, xx, yy, zz): # __init__() is initialization\n", " # self is refering to an obj in this class\n", " # xx, yy, zz are the input the user gives\n", " self.x = xx\n", " self.y = yy\n", " self.z = zz\n", " \n", " # compute the norm\n", " def norm(self):\n", " return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)\n", "\n", " # divide by the norm\n", " def normalize(self): \n", " # this returns the unit vector in the same direction with a Vector obj\n", " # if a function/method has only self as input inside\n", " # the class definition, in executing this function outside\n", " # input can be left empty\n", " no = self.norm()\n", " self.x /= no # self.x = self.x/no\n", " self.y /= no\n", " self.z /= no \n", " \n", "# note that every function has self in it, so that it can access the information of the class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `class` specifies what constitutes an object (the variables in it, which are all the variables that appear as `self.---`), and the functions that can be used. To create an object of the class, a.k.a. an instance of the class, we need to call the name of the class as if it's a function. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v = Vector(1.0,2.0,3.0) \n", "# the arguments of this call must match the arguments of the __init__ \n", "# function in the class, except the self part" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "u = Vector(2.0, 8.0) # this gives us error b/c zz is missing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(v)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(v.x)\n", "print(v.y)\n", "print(v.z)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v.normalize() # normalize function in Vector class is applied on v\n", "print(v.x, v.y, v.z)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(v)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### What happened?\n", "`print` gives us something that tells you that `v` is a\n", "vector at some memory location. Whenever we invoke Python's `print` command, it first applies the Python `repr` function to the item you are printing. The `repr` returns a string containing a printable\n", "version of the object you are printing. We can customize this behavior by adding the `__repr__(self)` function in this class." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "class Vector():\n", " \n", " def __init__(self, xx, yy, zz):\n", " self.x = xx\n", " self.y = yy\n", " self.z = zz\n", " \n", " def norm(self):\n", " return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)\n", "\n", " def normalize(self):\n", " no = self.norm()\n", " self.x /= no\n", " self.y /= no\n", " self.z /= no \n", " \n", " def __repr__(self):\n", " # return \"<\" + str(self.x)[:5] + \" , \" + str(self.y)[:5] + \" , \" + str(self.z)[:5] + \">\"\n", " return \"(\" + str(self.x) + \" , \" + str(self.y) + \" , \" + str(self.z) + \")\"\n", "# note that every function has self in it, so that it can access the information of the class" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v = Vector(1.0,2.00000001,3.04383)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(v) # a little buggy if we just display using 5 chacracters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This should remind of how we used `xs.append(5)` for a list xs. Indeed, `list` is a class just like our `Vector` class, and there is a function called `append(self, x)` in it that adds an extra element. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's also add dot products, scalar multiplication:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "class Vector():\n", " \n", " def __init__(self, xx, yy, zz):\n", " self.x = xx\n", " self.y = yy\n", " self.z = zz\n", " \n", " def norm(self):\n", " return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)\n", "\n", " def normalize(self):\n", " no = self.norm()\n", " self.x /= no\n", " self.y /= no\n", " self.z /= no \n", " \n", " def __repr__(self):\n", " return \"<\" + str(self.x) + \" , \" + str(self.y) + \" , \" + str(self.z) + \">\"\n", " \n", " def dot(self, w):\n", " # dot function's syntax will be v.dot(w)\n", " return self.x * w.x + self.y * w.y + self.z * w.z\n", " \n", " # returns a new vector without modifying the original\n", " def times_scalar(self, alpha):\n", " return Vector(alpha * self.x, alpha * self.y, alpha * self.z)\n", "# return (alpha * self.x, alpha * self.y, alpha * self.z) \n", " # this does not return to an obj in the original class\n", " \n", "# note that every function has self in it, so that it can access the information of the class" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Vector(1.0,2.0,3.0).dot(Vector(1.0,1.0,1.0)) # v.dot(w)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Vector(1.0,2.0,3.0).times_scalar(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Vector(1.0, 2.0, 3.0) + Vector(0.0, 5.0, 10.0) # this gives us error...what?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also write addition of vectors, we could write `add(self,w)` in the class and use `v.add(w)` to add `v` and `w`, but we could also **overload** the `+` operator as follows:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math\n", "class Vector():\n", " \n", " def __init__(self, xx, yy, zz):\n", " self.x = xx\n", " self.y = yy\n", " self.z = zz\n", " \n", " def norm(self):\n", " return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)\n", "\n", " def normalize(self):\n", " no = self.norm()\n", " self.x /= no\n", " self.y /= no\n", " self.z /= no \n", " \n", " def __repr__(self):\n", " return \"<\" + str(self.x) + \" , \" + str(self.y) + \" , \" + str(self.z) + \">\"\n", " \n", " def dot(self, w):\n", " # every function in a class needs self as the first argument\n", " return self.x * w.x + self.y * w.y + self.z * w.z\n", " \n", " def __add__(self, w):\n", " # this function extends the built-in + for Vector class\n", " return Vector(self.x + w.x, self.y + w.y, self.z + w.z)\n", " \n", "# note that every function has self in it, so that it can access the information of the class" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(Vector(1.0, 2.0, 3.0) + Vector(0.0, 5.0, 10.0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Vector.dot(Vector(1.0, 2.0, 3.0),Vector(0.0, 5.0, 10.0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Vector(1.0, 2.0, 3.0).dot(Vector(0.0, 5.0, 10.0)) # same with above" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 1:\n", "Create a method (function) for `Vector` class that computes the cross product of a vector with another." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercise 2:\n", "\n", "Create a class called `Line` which represents a line with equation $ax + by + c = 0$. Write the `__init__(self,a,b,c)`, `__repr__(self)` and `intersect(self,other_line)` methods for the class. The intersect method should return the coordinates of the intersection point of two lines. \n" ] } ], "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.2" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": true, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }