{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(classes)=\n", "# Python classes\n", "\n", "A very popular concept in Computer Science is *an object*. Objects are theoretical entities that may contain data (*fields*) as well as specific functionalities (*methods*). Classes in Python provide a means of implementing *types* objects. \n", "\n", "Oof... that is a lot of abstract terminology. Let us have a look at an example:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "woof-woof!\n", "woof-woof!\n", "k\n" ] } ], "source": [ "class Dog:\n", " \n", " # initialise an INSTANCE of the object\n", " def __init__(self, name):\n", " # assign the supplied name to the Dogs name\n", " self.name = name\n", " \n", " # only dogs can bark\n", " def bark(self):\n", " print(\"woof-woof!\") \n", " \n", "# lets make some dogs \n", "d1 = Dog(\"Princess\")\n", "d2 = Dog(\"Chloe\")\n", "d3 = Dog(\"Spooky\")\n", "\n", "# make them bark\n", "d1.bark()\n", "d3.bark()\n", "\n", "print(d2.name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`class Dog` defines a conceptual dog. In the simple model dogs only have a name and can bark, this is what characterises them.\n", "\n", "The `__init__` method is used when a dog is initialised e.g. in the ` d1 = Dog(\"Princess\")`. Now `d1` is a member of the `Dog` class as well as `d2` and `d3`. \n", "\n", "The `bark()` method is specific to dogs, so we can call it on any instance of a `Dog`. \n", "\n", "**IMPORTANT**: Notice the `self` keyword in the definition. `self` will always be used as the first argument of the class methods. `self` is also used to reference the fields of the object (e.g. `self.name`).\n", "\n", "Let us give the `Dog` class a bit more functionality:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class Dog:\n", " \n", " def __init__(self, name):\n", " self.name = name\n", " self.happy = True\n", " \n", " def makeHappy(self):\n", " self.happy = True\n", " \n", " def makeSad(self):\n", " self.happy = False\n", "\n", " def bark(self):\n", " print(\"woof-woof!\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have methods that allow us to set the value of the `happy` attribute. Every dog is happy when instantiated. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inheritance\n", "\n", "Some concepts can be treated as subsets of others. In this inheritance relation we distinguish two types of classes:\n", "\n", "1) **Parent class** (base class): the class being inherited\n", "\n", "2) **Child class**: the class that inherits\n", "\n", "Consider the following two classes:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ha ha! I am always happy! Can't make me sad!\n", "woof-woof!\n" ] } ], "source": [ "class Dog:\n", " \n", " def __init__(self, name):\n", " self.name = name\n", " self.happy = True\n", " \n", " def makeHappy(self):\n", " self.happy = True\n", " \n", " def makeSad(self):\n", " self.happy = False\n", "\n", " def bark(self):\n", " print(\"woof-woof!\")\n", " \n", "class HappyDog(Dog):\n", " \n", " def __init__(self, name):\n", " super().__init__(name)\n", " \n", " def makeSad(self):\n", " print(\"Ha ha! I am always happy! Can't make me sad!\")\n", " \n", "hd1 = HappyDog(\"Scooby\")\n", "\n", "hd1.makeSad()\n", "hd1.bark()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can probably see, `HappyDog` inherits the methods from the `Dog` class. It also has its version of the `makeSad` method. We say that `HappyDog` class overrides the `makeSad` method of the `Dog` class. We use the `super()` method to denote inheritance from the `Dog` class.\n", "\n", "Whereas child classes can have multiple parent classes, it usually complicates the code and is not considered a good practice." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Classes without `__init__`\n", "\n", "Classes without the `__init__` method cannot produce objects, they cannot be instantiated. They can still provide functionality, however. On a basic level, they can be treated as our own library. Notice that there is no `self` argument in the methods of such class:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 + 0i\n", "2 + 2i\n" ] } ], "source": [ "#regular class\n", "class ComplexNum():\n", " def __init__(self, real, im):\n", " self.real = real\n", " self.im = im\n", " \n", " def __str__(self):\n", " return str(self.real) +\" + \" + str(self.im)+\"i\"\n", " \n", "#without __init__\n", "class ComplexMath():\n", " \n", " def add(com1,com2):\n", " return ComplexNum(com1.real+com2.real, com1.im+com2.im)\n", " \n", " def mul(com1,com2):\n", " return ComplexNum(com1.real*com2.real-com1.im*com2.im,com1.im*com2.real+com1.real*com2.real)\n", " \n", " \n", "com1 = ComplexNum(1,1)\n", "com2 = ComplexNum(1,-1)\n", "\n", "print(ComplexMath.add(com1,com2))\n", "print(ComplexMath.mul(com1,com2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here `ComplexNum` is a regular class used to represent complex numbers. `ComplexMath` provides some basic arithmetic operations on those numbers. There is no need to instantiate it, we just need the functionality. \n", "\n", "**REMARK**: `__str__` method must be defined if you want to have a nice way of printing the instances of your class.\n", "\n", "Classes are a very broad field of programming in Python, and this is just a brief introduction. We will explore classes in the [Fundamentals of Computer Science](LINK) more." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(classes_exercises)=\n", "## Exercises\n", "\n", "* **Aliens** Define an `Alien` class which instantiates aliens with `age` equal to 1. It should also be able to increase the age of an alien by 1 by calling the `birthday()` method." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Answer\n", ":class: dropdown\n", "\n", "```python\n", "class Alien:\n", " \n", " def __init__(self):\n", " self.age = 1\n", " \n", " def birthday(self):\n", " self.age+=1 \n", "```\n", "\n", "````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* **Living Aliens** Now upgrade your `Alien` class. Alien is instantiated with the field `isAlive` set to `True`. Now, an alien can `considerDying` by taking a random integer from 0 to 10 (inclusive) and seeing if it is smaller than its age. If it is, the alien dies. You should also add the following method to the class definition:\n", "\n", "```python\n", "def reproduce(self):\n", " return self.isAlive and random.randint(0,6) > self.age\n", " \n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Answer\n", ":class: dropdown\n", "\n", "```python\n", "import random\n", " \n", "class Alien:\n", " \n", " def __init__(self):\n", " self.age = 1\n", " self.isAlive = True\n", " \n", " def birthday(self):\n", " self.age+=1\n", " \n", " def considerDying(self):\n", " if random.randint(0,10) < self.age:\n", " self.isAlive = False\n", " \n", " def reproduce(self):\n", " return self.isAlive and random.randint(0,6) > self.age\n", "\n", "```\n", "\n", "````" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* **Simulating population** Now let us simulate the population of aliens which can die and reproduce. \n", "\n", "1) On each time step (`for` loop) we will make all aliens celebrate `birthday`, `reproduce` and `considerDying`. \n", "\n", "2) An alien should be removed from the population when it dies. \n", "\n", "3) Also, when reproduction is successful, a new alien is added to the population. Make sure to add the new aliens at the end of the time step, so they are not considered it the timestep they were born in. \n", "\n", "4) Do the simulation for `100` timesteps. \n", "\n", "5) You might want to print the size of the population vs time using matplotlib.\n", "\n", "6) Start with only one alien in the population.\n", "\n", "**REMARK**: This model of the population does not ensure that the size of the population will be stable. For now, the population ceases in the first couple of days or grows exponentially. If you want to explore the simulation more, consider adding population size in `reproduce` method, it should limit the exponential growth. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "````{admonition} Answer\n", ":class: dropdown\n", "\n", "```python\n", "\n", "import random\n", "import matplotlib.pyplot as plt\n", " \n", "class Alien:\n", " \n", " def __init__(self):\n", " self.age = 1\n", " self.isAlive = True\n", " \n", " def birthday(self):\n", " self.age+=1\n", " \n", " def considerDying(self):\n", " if random.randint(0,10) < self.age:\n", " self.isAlive = False\n", " \n", " def reproduce(self):\n", " return self.isAlive and random.randint(0,6) > self.age\n", "\n", "\n", "aliens = [Alien()]\n", "timeSeries = []\n", " \n", "for i in range(100):\n", "\n", " \n", " aliensToAdd = 0\n", " \n", " for alien in aliens:\n", " alien.birthday()\n", " if alien.reproduce():\n", " aliensToAdd+=1\n", " alien.considerDying()\n", " \n", " aliens = [x for x in aliens if x.isAlive]\n", " \n", " for new in range(aliensToAdd):\n", " aliens.append(Alien())\n", " \n", " timeSeries.append(len(aliens))\n", " \n", "x = list(range(100))\n", "y = timeSeries\n", "\n", "plt.plot(x,y)\n", "plt.show()\n", "\n", "```\n", "\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.4" } }, "nbformat": 4, "nbformat_minor": 2 }