From 496b20583fe8936f16d084adb7d10edf58066865 Mon Sep 17 00:00:00 2001 From: Amir Farbin Date: Mon, 12 Feb 2024 12:39:52 -0600 Subject: [PATCH] Lecture 7/8 --- Lectures/Lecture.7/Lecture.7.ipynb | 164 ++--- Lectures/Lecture.8/Lecture.8.ipynb | 1052 ++++++++++++++++++++++++++++ 2 files changed, 1136 insertions(+), 80 deletions(-) create mode 100755 Lectures/Lecture.8/Lecture.8.ipynb diff --git a/Lectures/Lecture.7/Lecture.7.ipynb b/Lectures/Lecture.7/Lecture.7.ipynb index afd3b99..33b45c5 100755 --- a/Lectures/Lecture.7/Lecture.7.ipynb +++ b/Lectures/Lecture.7/Lecture.7.ipynb @@ -11,8 +11,34 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "## Mean/Variance\n", + "\n", + "Also for lab 3, remember the equations for mean/variance. If you have a data sample ${x_1, x_2, ..., x_N}$ the mean is:\n", + "\n", + "$$ \n", + "\\bar{x} = \\frac{1}{N}\\sum_{i=1}^{N} x_i\n", + "$$\n", + "\n", + "and the variance is:\n", + "\n", + "$$\n", + " = \\frac{1}{N-1} \\sum_{i=1}^{N} (x_i - \\bar{x})^2\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Random Number Generator\n", + "\n", + "We have learned about probability distributions and how data is generally a collection of random variables drawn from probability distributions. \n", + "\n", + "We also have discussed that analysis of a dataset is often really just trying to characterize the probability distributions of the random variables. Once we understand those probability distributions, we can then make predictions about future data.\n", + "\n", + "But we can also create (e.g. simulate) new data from the probability distributions. \n", "\n", - "## Random Number Generator" + "So how do we get a computer to generate data (e.g. random variables) from probability distributions? First, think about generating random numbers in a computer." ] }, { @@ -27,53 +53,6 @@ "import numpy as np" ] }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['In',\n", - " 'Out',\n", - " '_',\n", - " '__',\n", - " '___',\n", - " '__builtin__',\n", - " '__builtins__',\n", - " '__doc__',\n", - " '__loader__',\n", - " '__name__',\n", - " '__package__',\n", - " '__spec__',\n", - " '_dh',\n", - " '_i',\n", - " '_i1',\n", - " '_i2',\n", - " '_ih',\n", - " '_ii',\n", - " '_iii',\n", - " '_oh',\n", - " 'exit',\n", - " 'get_ipython',\n", - " 'np',\n", - " 'open',\n", - " 'plt',\n", - " 'quit',\n", - " 'x']" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x=5\n", - "dir()" - ] - }, { "cell_type": "code", "execution_count": 3, @@ -271,25 +250,6 @@ "In your solution, you'll most likely generate $x_0$ one by one, compute $x$, and store $x$ into a list to be returned from your function.\n" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Mean/Variance\n", - "\n", - "Also for lab 3, remember the equations for mean/variance. If you have a data sample ${x_1, x_2, ..., x_N}$ the mean is:\n", - "\n", - "$$ \n", - "\\bar{x} = \\frac{1}{N}\\sum_{i=1}^{N} x_i\n", - "$$\n", - "\n", - "and the variance is:\n", - "\n", - "$$\n", - " = \\frac{1}{N-1} \\sum_{i=1}^{N} (x_i - \\bar{x})^2\n", - "$$\n" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -810,6 +770,17 @@ "3 ways to do the same thing:" ] }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "y_vals = list()\n", + "for x in x_vals:\n", + " y_vals.append(a_function(x))" + ] + }, { "cell_type": "code", "execution_count": 29, @@ -837,17 +808,6 @@ "print(y_vals)" ] }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [], - "source": [ - "y_vals = list()\n", - "for x in x_vals:\n", - " y_vals.append(a_function(x))" - ] - }, { "cell_type": "code", "execution_count": 32, @@ -1105,7 +1065,51 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Note that `find_min` can be rewritten as a single evaluation:" + "Now lets write `find_min` in a more function way. Here is the original again:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def find_min_0(f,x_min,x_max,steps=10):\n", + " \n", + " step_size=(x_max-x_min)/steps\n", + " x=x_min\n", + " y_min=f(x_min)\n", + " x_min_val=x_min\n", + "\n", + " for i in range(steps):\n", + " y=f(x)\n", + " if y\n", + "The student instance: \n" + ] + } + ], + "source": [ + "a_student=student()\n", + "\n", + "print(\"The student class:\", type(student))\n", + "print(\"The student instance:\", type(a_student))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Most built-in and third party python components are also object oriented:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n" + ] + } + ], + "source": [ + "print(type(str))\n", + "print(type(str()))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "a = np.ndarray([1,2,3])\n", + "print(type(a))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "John\n" + ] + } + ], + "source": [ + "a_student.name = \"John\"\n", + "print(a_student.name)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['__class__',\n", + " '__delattr__',\n", + " '__dict__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__format__',\n", + " '__ge__',\n", + " '__getattribute__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__module__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__str__',\n", + " '__subclasshook__',\n", + " '__weakref__',\n", + " 'add_grade',\n", + " 'average_grade',\n", + " 'grades',\n", + " 'id_number',\n", + " 'name',\n", + " 'year']" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dir(student)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "a_student.add_grade(100.)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[100.0]\n" + ] + } + ], + "source": [ + "print(a_student.grades)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "a_student_1=student()\n", + "a_student_1.add_grade(50.)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[100.0, 50.0]\n", + "[100.0, 50.0]\n" + ] + } + ], + "source": [ + "print(a_student.grades)\n", + "print(a_student_1.grades)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that because of how we declared the attributes of our class, all instances of the class seem to share some of the same attributes. We'll fix that in a bit..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why OO?\n", + "\n", + "Why would such a construction be helpful? An alternative way of keeping all doing all of the book-keeping for the students would have been to create a bunch of lists for each of the attributes and make sure that the first student's information is always at index 0, second student index 1, and so on. \n", + "\n", + "For exmaple:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "names=list()\n", + "id_numbers=list()\n", + "years=list()\n", + "grades=list()\n", + "\n", + "# Create an \"instance\" of a student\n", + "\n", + "names.append(str())\n", + "id_numbers.append(int())\n", + "years.append(str())\n", + "grades.append(list())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could write functions to help make all of this look nicer, but it would be cumbersome to manage and ugly to read. By encapsulating concepts into objects, we ultimately reduce the complexity of your code.\n", + "\n", + "Note that python dictionaries would allow us to in build structures that are similar to an object oriented class, but with no defined structure, every data member and operation would have to managed by convention and not enforced by the langauge.\n", + "\n", + "## Constructor / Destructor\n", + "\n", + "We created an instance of student in the example above, but we didn't take care to carefully make sure that the student instance was carefully setup. The first important OO concept are **constructors** and **destructors**. These are optional methods that are called when an object is created or destroyed. Since python manages memory for us, we typically don't need to implement destructors, but constructors are always a good idea. \n", + "\n", + "In python the names of build-in methods of classes typically start and end with 2 underscores. `__init__(self,...)` and `__del__(self)` are class constructor and destructors, respectively. \n", + "\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "class student:\n", + " def __init__(self, name, id_number, year):\n", + " self.name=name\n", + " self.id_number=id_number\n", + " self.year=year\n", + " self.grades=list()\n", + " \n", + " def add_grade(self,grade):\n", + " self.grades.append(grade)\n", + " \n", + " def average_grade(self):\n", + " return sum(self.grades)/len(self.grades)\n", + " \n", + " def print_grades(self):\n", + " for grade in self.grades:\n", + " print(grade)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now when you instantiate a student you would do:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "85\n", + "90\n", + "Average: 87.5\n" + ] + } + ], + "source": [ + "a_student=student(\"John Doe\", 111, 0)\n", + "\n", + "a_student.add_grade(85)\n", + "a_student.add_grade(90)\n", + "\n", + "a_student.print_grades()\n", + "\n", + "print(\"Average:\", a_student.average_grade())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And you can keep all of the information for all of your students in a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "students=list()\n", + "\n", + "students.append(student(\"Jim Doe\", 111, 0))\n", + "students.append(student(\"Jane Doe\", 112, 0))\n", + "\n", + "print(type(students[0]))\n", + "students[0].add_grade(100)\n", + "students[0].add_grade(99)\n", + "students[1].add_grade(89)\n", + "students[1].add_grade(100)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Name: Jim Doe\n", + "Average: 99.5\n", + "Name: Jane Doe\n", + "Average: 94.5\n" + ] + } + ], + "source": [ + "for student in students:\n", + " print(\"Name:\", student.name)\n", + " print(\"Average:\", student.average_grade())" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['__class__',\n", + " '__delattr__',\n", + " '__dict__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__format__',\n", + " '__ge__',\n", + " '__getattribute__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__module__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__str__',\n", + " '__subclasshook__',\n", + " '__weakref__',\n", + " 'add_grade',\n", + " 'average_grade',\n", + " 'grades',\n", + " 'id_number',\n", + " 'name',\n", + " 'print_grades',\n", + " 'year']" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dir(student)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are lots of built-in methods for classes, some of which have default implementations that you can **overload**, others that you can optionally implement. For example, if you have objects that you want python to know how to add, you can implement `__add__(self,other)` method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Object oriented programming allows you to establish and maintain abstractions more effectively. Once an object has been implemented, users of the object don't need to know how the internals work and how it stores data to use it. They simply use the object's methods. It also makes it easier to change implementations. As long as the class maintains the same method names (aka interface), you can improve and evolve the data and methods as you like without affecting any application that uses your class." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classes and Instances\n", + "\n", + "Consider the following simple class and instances." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, my name is Bob\n", + "Hello, my name is Bill\n" + ] + } + ], + "source": [ + "class person:\n", + " def __init__(self, name):\n", + " self.name = name\n", + " \n", + " def say_hello(self):\n", + " print(\"Hello, my name is \" + self.name)\n", + " \n", + "# create objects\n", + "bob = person(\"Bob\")\n", + "bill = person(\"Bill\")\n", + " \n", + "# call methods owned by virtual objects\n", + "bob.say_hello()\n", + "bill.say_hello()" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, my name is Jane\n", + "Hello, my name is John\n" + ] + } + ], + "source": [ + "people = [person(\"Jane\"),person(\"John\")]\n", + "Jane = people[0]\n", + "Jane.say_hello()\n", + "people[1].say_hello()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Inheritance\n", + "\n", + "A powerful feature of object-oriented programming is inheritance, which allows you to build a hierarchy of classes. For example, what if we wanted to keep track of students and faculty at the University. There would be some aspects of students and faculty that would be in common, while other would be different. We can store the common atrributes and methods in a common class called \"person\" that both \"student\" and \"faculty\" **inherit** from. \n", + "\n", + "For example:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "class person:\n", + " def __init__(self, name, id_number):\n", + " self.name=name\n", + " self.id_number=id_number\n", + " \n", + " \n", + "class student(person):\n", + " def __init__(self, name, id_number, year):\n", + " super(student,self).__init__(name,id_number)\n", + " # person(name,id_number)\n", + " self.year=year\n", + " self.grades=list()\n", + " \n", + " def add_grade(self,grade):\n", + " self.grades.append(grade)\n", + " \n", + " def average_grade(self):\n", + " return sum(self.grades)/len(self.grades)\n", + " \n", + " def print_grades(self):\n", + " for grade in self.grades:\n", + " print(grade)\n", + "\n", + " \n", + "class faculty(person):\n", + " def __init__(self, name, id_number):\n", + " super(faculty,self).__init__(name,id_number)\n", + " self.courses=list()\n", + " \n", + " def add_courses(self,course):\n", + " self.grades.append(course)\n", + " \n", + " def print_courses(self):\n", + " for courses in self.courses:\n", + " print(course)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Inheritance helps in reducing the need to copy and paste code and to make it easier to use your code by establishing common interface and behaviors between different objects." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Public and Private Methods\n", + "\n", + "By convention, methods that start with two underscores (`__`) are considered to be private and are meant to be only called by the class." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "updating software\n", + "operate\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "'device' object has no attribute '__update'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[21], line 16\u001b[0m\n\u001b[1;32m 13\u001b[0m a_device\u001b[38;5;241m.\u001b[39moperate()\n\u001b[1;32m 15\u001b[0m \u001b[38;5;66;03m# This will fail\u001b[39;00m\n\u001b[0;32m---> 16\u001b[0m \u001b[43ma_device\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__update\u001b[49m()\n", + "\u001b[0;31mAttributeError\u001b[0m: 'device' object has no attribute '__update'" + ] + } + ], + "source": [ + "class device: \n", + " def __init__(self):\n", + " self.__update()\n", + " \n", + " def operate(self):\n", + " print('operate')\n", + " \n", + " def __update(self):\n", + " print('updating software')\n", + " \n", + "a_device = device()\n", + "\n", + "a_device.operate()\n", + "\n", + "# This will fail\n", + "a_device.__update()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Public and Private Data\n", + "\n", + "Usually a class needs to control the data it holds. If an external class or user changes a data member of a class in a unexpected way, then the class can fail.\n", + "\n", + "The way to control the data in your classes is to make the varibles holding the data private and create \"setter\" and \"accessor\" functions." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Unnamed\n", + "Geronimo\n", + "1\n" + ] + } + ], + "source": [ + "class car:\n", + " def __init__(self,name=\"Unnamed\",n_doors=4, max_passengers=4):\n", + " self.__n_passengers = 0\n", + " self.__name=name\n", + " self.__n_doors=n_doors\n", + " self.__max_passengers=max_passengers\n", + " \n", + " ## Accessors / Getters\n", + " def name(self):\n", + " return self.__name\n", + " \n", + " def n_doors(self):\n", + " return self.__n_doors\n", + " \n", + " def n_passengers(self):\n", + " return self.__n_passengers\n", + "\n", + " def max_passengers(self):\n", + " return self.__max_passengers\n", + "\n", + "\n", + " ## Setter\n", + " def set_name(self,name):\n", + " if isinstance(name,str):\n", + " self.__name=name\n", + " else:\n", + " print (\"Name must be a string.\")\n", + " \n", + " ## Can't change number of doors on a car... so no setter for __n_doors\n", + " \n", + " ## We can only add and remove passengers\n", + " def add_passenger(self,n=1):\n", + " if isinstance(n,(int,float)):\n", + " self.__n_passengers+=n\n", + " if self.__n_passengers>self.__max_passengers:\n", + " self.__n_passengers=self.__max_passengers\n", + " print (\"Car is full. \",n-self.max_passengers,\" passengers were left outside.\")\n", + " else:\n", + " print (\"Number of passengers must be an integer.\")\n", + " \n", + " def remove_passenger(self,n=1):\n", + " if isinstance(n,int):\n", + " self.__n_passengers-=n\n", + " if self.__n_passengers<0:\n", + " self.__n_passengers=0 \n", + " else:\n", + " print(\"Number of passengers must be an integer.\")\n", + "\n", + "\n", + "\n", + "my_car=car()\n", + "print (my_car.name())\n", + "my_car.set_name(\"Geronimo\")\n", + "print (my_car.name())\n", + "\n", + "my_car.add_passenger()\n", + "print (my_car.n_passengers())\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Method Overloading\n", + "\n", + "**Overloading** refers to the ability to define new data or methods for a child class that \"overload\" the same data or methods of a parent class. A common coding pattern is to create a shared parent class for two or more children classes. The parent class defines a set of data and methods which are either not implemented at all (referred to as **virtual** method) or provide some default behavior. The child classes overload these methods to implement their own specializations. \n", + "\n", + "For example:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Learning...\n", + "Teaching...\n" + ] + } + ], + "source": [ + "class person:\n", + " __name=\"\"\n", + " def __init__(self, name):\n", + " self.__name=name\n", + " \n", + " # This is a virtual method\n", + " def do_work(self):\n", + " raise NotImplementedError\n", + " \n", + "class student(person): \n", + " def __init__(self, name, year):\n", + " person.__init__(self,name)\n", + " self.__year=year\n", + " self.__grades=list()\n", + "\n", + " def add_grade(self,grade):\n", + " self.__grades.append(grade)\n", + " \n", + " def average_grade(self):\n", + " return sum(self.__grades)/len(self.__grades)\n", + " \n", + " def print_grades(self):\n", + " for grade in self.__grades:\n", + " print (grade)\n", + " \n", + " def do_work(self):\n", + " print (\"Learning...\")\n", + "\n", + " \n", + "class faculty(person):\n", + " def __init__(self, name):\n", + " person.__init__(self,name)\n", + " self.__courses=list()\n", + "\n", + " def add_courses(self,course):\n", + " self.__courses.append(course)\n", + " \n", + " def print_courses(self):\n", + " for course in self.__courses:\n", + " print (course)\n", + "\n", + " def do_work(self):\n", + " print (\"Teaching...\")\n", + "\n", + " \n", + " \n", + "a_student=student(\"Bob\",2)\n", + "a_teacher=faculty(\"Mary\")\n", + "\n", + "a_student.do_work()\n", + "a_teacher.do_work()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the parent class, in this case, has some methods that should no be used because they are not implemented. Indeed the parent class should probably never be instanciated either." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "ename": "NotImplementedError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNotImplementedError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[24], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m a_person\u001b[38;5;241m=\u001b[39mperson(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mJohn\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m \u001b[43ma_person\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdo_work\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[23], line 8\u001b[0m, in \u001b[0;36mperson.do_work\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdo_work\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[0;32m----> 8\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m\n", + "\u001b[0;31mNotImplementedError\u001b[0m: " + ] + } + ], + "source": [ + "a_person=person(\"John\")\n", + "a_person.do_work()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In object oriented programming, classes that shouldn't be instanciated are known as **abstract** and methods that shouldn't be called are known as **virtual**. Some languages (usually compiled ones) don't allow you to instanciate objects that are abstract or have virtual methods. Python is not as strict and some of the usual contructs of object oriented programming are conventions and not enforced." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Polymorphism\n", + "\n", + "In the scenarios described above, since the parent class `person` defines a `do_work()` method, any derived child class will necessarily have `do_work()` available. For example, I can create a list of people and make them work:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Learning...\n", + "Teaching...\n" + ] + } + ], + "source": [ + "people= [student(\"Bob\",2), faculty(\"Mary\")]\n", + "\n", + "for person in people:\n", + " person.do_work()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We note that the code here doesn't have to know anything about `student` or `faculty`. It just has to know about `person`. \n", + "\n", + "But what if I have a list of people and I want to check if a specific object in the list is an instance of a specific class so I can call methods of that class?\n", + "\n", + "You can simply check if the object is an instance of a specfic class:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(a_student, student)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Therefore calling methods specific to the derived class:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Learning...\n", + "Teaching...\n", + "100.0\n", + "Data 3401\n" + ] + } + ], + "source": [ + "for person in people:\n", + " person.do_work()\n", + " if isinstance(person,student):\n", + " person.add_grade(100.)\n", + " if isinstance(person,faculty):\n", + " person.add_courses(\"Data 3401\")\n", + "\n", + "people[0].print_grades()\n", + "people[1].print_courses()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overloading Built-ins\n", + "\n", + "I found [this](https://realpython.com/operator-function-overloading/) walk through of overloading python operators to be very well done, so lets go through it.\n", + "\n", + "For a complete list of operators, look at the table at the bottom of the [Operator Library referece](https://docs.python.org/3.7/library/operator.html).\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'abcabcabc'" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "'abc'*3" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "can only concatenate str (not \"int\") to str", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Input \u001b[0;32mIn [103]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mab\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\n", + "\u001b[0;31mTypeError\u001b[0m: can only concatenate str (not \"int\") to str" + ] + } + ], + "source": [ + "'ab'+2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}