{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "import pandas as pd\n", "import os" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lecture 10:\n", "\n", "- Learn about \"object oriented programming\" (OOP)\n", "\n", "- Learn how to create a \"class\"\n", "\n", "- Learn more about namespaces \n", "\n", "- Learn more about copies\n", "\n", " \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Object oriented programming\n", "\n", "Until now we haven't mentioned object oriented programming (OOP), yet we have been using **objects**from the beginning. Knowing how to create and use **objects** in Python is very powerful. Examples of **objects** that we have already encountered are the various data containers we have been using and things like plots. **Objects** have **methods** that can be used to change an object and **attributes** that describe features of an object. \n", "\n", "Now we will learn how to make our own objects with our own special blend of **attributes** and **methods**. The trick is to make a **class** and define it to have the desired **attributes** and **methods**. \n", "\n", "### Classes\n", "\n", "To create an object with methods, we use a **class** definition, which is a blueprint or recipe defining the **attributes** and **methods** associated with the **class**. When we call the class, we create an **instance** of the **class**, also known as an **object**. \n", "\n", "Here is an example of a **class** definition:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Circle:\n", " \"\"\"\n", " This is an example of a class called Circle\n", " \"\"\"\n", " import numpy as np # get some math power\n", " # define some attributes of the Circle class\n", " pi=np.pi # pi is now an attribute of this class too. \n", " # initialize the class with the attribute r (no parentheses when called)\n", " def __init__(self,r):\n", " self.r=r # define a variable, r\n", " # define some methods (these have parentheses when called)\n", " def area(self): \n", " return (1./2.)*self.pi*self.r**2\n", " def circumference(self):\n", " return 2.*self.pi*self.r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can create an **instance** of the Circle **class** called C with a radius of $r$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "r=3.0 # assign 3 to a variable r\n", "C=Circle(r) # create a class instance with radius of 3.0\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use any of the attributes or methods of this class like this:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The value of pi is: 3.141592653589793\n", "The radius of this circle is: 3.0\n", "The area of a circle with radius = 3.0 is: 14.137166941154069\n", "The circumference of that circle is: 18.84955592153876\n" ] } ], "source": [ "print (\"The value of pi is: \",C.pi) # no parentheses!\n", "print (\"The radius of this circle is: \",C.r)# no parentheses!\n", "print (\"The area of a circle with radius = \",r,'is: ',C.area()) # with parentheses!\n", "print (\"The circumference of that circle is: \",C.circumference()) # with parentheses!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also save the Circle class in a module, just as we did in earlier Lectures for functions. Then we can import it into other notebooks of scripts as desired. \n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Writing Shapes.py\n" ] } ], "source": [ "%%writefile Shapes.py\n", "class Circle:\n", " \"\"\"\n", " This is an example of a class called Circle\n", " \"\"\"\n", " import numpy as np # get some math power\n", " # define some attributes of the Circle class\n", " pi=np.pi # pi is now an attribute of this class too. \n", " # initialize the class with the attribute r (no parentheses when called)\n", " def __init__(self,r):\n", " self.r=r # define a variable, r\n", " # define some methods (these have parentheses when called)\n", " def area(self): \n", " return (1./2.)*self.pi*self.r**2\n", " def circumference(self):\n", " return 2.*self.pi*self.r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can use it! Here is an example how: " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.141592653589793\n" ] } ], "source": [ "import Shapes as S\n", "newCirc=S.Circle(6.0)\n", "print (newCirc.pi)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Attributes and methods\n", "\n", "You might be wondering about some things by now. For example, you should have noticed is that when we asked for **C.pi** there were no parentheses, but both **C.area( )** and **C.circumference( )** did have parentheses. Why? \n", "\n", "The answer is that __r__ and **pi** are **attributes**, and **area** and **circumference** are **methods**. Did you notice that the method definitions look a lot like functions, but are inside the class definition. A **method** really is a function, but it is special in that it belongs to a **class** and works on the **instance** of the **class**. They can only be called by using the name of the **instance**, followed by a dot, followed by the **method** (with parentheses). \n", "\n", "### More about classes\n", "\n", "Classes are not the same as functions. Although our **Shape** module can be imported just the same as any other module, to use it, we first have to create a class **instance** (**C=Shapes.Circle(r)**). \n", "\n", "\n", "All _**methods** (parts that start with **def**), have an **argument** list. The first **argument** has to be a reference to the class instance itself, which is always **self**, followed by any variables you want to pass into the **method**. \n", "\n", "The \"**\\_\\_init\\_\\_**\" method initializes the **instance** attributes. In the Circle class, the **\\_\\_init\\_\\_** method defined the **attribute** **r**, which gets passed in when the class is first called. \n", "Asking for any **attribute**, retrieves the current value of that **attribute**. \n", "\n", "But. Attributes can be changed:\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.0\n", "7.0\n" ] } ], "source": [ "print (C.r)\n", "C.r=7.\n", "print (C.r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To summarize: The **methods** (**area** and **circumference**) are defined just like any function except note the use of **self** as the first argument. This is required in all class method definitions. In our case, no other variables are passed in because the only one used is $r$, so the argument list consists of only **self**. Calling these **methods** requires no further arguments (the parentheses are empty) and the class returns the current values. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "76.96902001294993" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C.area()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can make a subclass (child) of the parent class which has all the attributes and methods of the parent, but may have a few attributes and methods of its own. You do this by setting up another class definition within a class. \n", "\n", "So, the bottom line about classes is that they are in the same category of things as variables, lists, dictionaries, etc. That is, they are \"data containers\" but with benefits. They hold data, and they also hold the methods to process those data.\n", "\n", "\n", "If you are curious about classes, there's lots more to know about them that we don't have time to get into. You can find useful tutorials online: https://www.python-course.eu/python3_object_oriented_programming.php\n", "\n", "or \n", "\n", "http://www.sthurlow.com/python/lesson08/ [but be careful with this one as it is for Python 2.7, so the **print** statements won't work without parentheses, e.g., **print ('this way')**, not, **print 'not this way'**. ] \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Namespaces\n", "\n", "Another thing you might be wondering about is why did we import **NumPy** inside the class definition when it was imported into the notebook at the top? The answer is we didn't have to. The class definition works perfectly well without it in this case. But if we don't import **Numpy** within in the Shape module, the module won't work at all because it doesn't \"know\" about **NumPy**. So in the module, you have to import whatever you need to run the module. \n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Copies\n", "\n", "Another issue we have been tiptoeing around is the concept of a copy of an object and what that means. In Python, this can be a bit confusing. When we define some simple variables, the behavior is pretty much what you might expect: " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n", "3\n" ] } ], "source": [ "x=3 # define x\n", "y=x # set y equal to x\n", "print (y) # print out y\n", "x=4 # change the value of X\n", "print (y) # and y is still equal to its first definition. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But if we define a list object (a _compound_ object with more than one variable), things get weird:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['spam', 'ocelot', 42]\n", "['spam', 'ocelot', 'not an ocelot']\n" ] } ], "source": [ "L1=['spam','ocelot',42] # define the list\n", "L2=L1 # make a copy of the list\n", "print (L2) # print the copy\n", "L1[2]='not an ocelot' # change the original\n", "print (L2) # and oops - the copy got changed too!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This means that **L1** and **L2** refer to the SAME OBJECT. So how do I make a copy that is its own object (doesn't change)? For simple lists (that do not contain sublists), we already learned how to do this:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['spam', 'ocelot', 'not an ocelot']\n", "['spam', 'ocelot', 'not an ocelot']\n" ] } ], "source": [ "L3=L1[:]\n", "print (L3)\n", "L1[2]=42\n", "print (L3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This approach breaks down if the object is more complicated. The copies will sometimes be subject to mutation. (Try this yourself!). \n", "\n", "To avoid this problem, there is a module called **copy** with a function called **deepcopy**, which will make an independent copy of the object in question:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "L2: ['spam', 'ocelot', 42]\n", "L1: ['spam', 'ocelot', 'not an ocelot']\n", "L2: ['spam', 'ocelot', 42]\n" ] } ], "source": [ "from copy import deepcopy\n", "\n", "L1=['spam','ocelot',42] # define the list\n", "L2=deepcopy(L1) # make a copy of the list\n", "print (\"L2: \",L2) # print the copy\n", "L1[2]='not an ocelot' # change the original\n", "print (\"L1: \",L1)\n", "print (\"L2: \",L2) # and bingo, L2 didn't " ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# clean up\n", "os.remove('Shapes.py')" ] } ], "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.7" } }, "nbformat": 4, "nbformat_minor": 1 }