{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 8.4 Operator overloading" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Estimated time for this notebook: 15 minutes*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've seen already during the course that some operators behave differently depending on the data type.\n", "\n", "For example, `+` adds numbers but concatenates strings or lists:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "4 + 2" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'42'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"4\" + \"2\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`*` is used for multiplication, or repeated addition:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "42" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "6 * 7" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'mememe'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"me\" * 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`/` is division for numbers, and wouldn't have a real meaning on strings. However, it's used to separate files and directories on your file system. Therefore, this has been *overloaded* in the `pathlib` module:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['anotherfile.py',\n", " '07_04_object_oriented_design.ipynb',\n", " 'species.py',\n", " '07_00_introduction.ipynb',\n", " '07_02_comments.ipynb',\n", " 'config.yaml',\n", " '07_07_refactoring_boids.ipynb',\n", " '__pycache__',\n", " '07_03_refactoring.ipynb',\n", " 'context.py',\n", " 'conventions.py',\n", " '07_06_design_patterns.ipynb',\n", " 'index.md',\n", " '07_05_classes.ipynb',\n", " '.ipynb_checkpoints',\n", " '07_01_coding_conventions.ipynb',\n", " 'fixed.png']" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os\n", "from pathlib import Path\n", "\n", "performance = Path(\"..\") / \"module07_construction_and_design\"\n", "os.listdir(performance)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above works because one of the elements is a `Path` object. Note, that the `/` works similarly to `os.path.join()`, so whether you are using Unix file systems or Windows, `pathlib` will know what path separator to use." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "performance = os.path.join(\"..\", \"module07_construction_and_design\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overloading operators for your own classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have seen that in Python operators do different things, how can we use `+` or other operators on our own classes to achieve similar behaviour?\n", "\n", "Let's go back to our Maze example, and simplify our room object so it's defined as:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now create a room as:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Room object at 0x1051f5110>\n" ] } ], "source": [ "small = Room(\"small\", 9)\n", "print(small)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, when we print it we don't get much infomation on the object. So, the first operator we are overloading is its string represenation defining `__str__`:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area\n", "\n", " def __str__(self):\n", " return f\"\"" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "small = Room(\"small\", 9)\n", "print(small)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How can we add two rooms together? What does it mean? Let's define that the addition (`+`) of two rooms makes up one with the combined size. We produce this behaviour by defining the `__add__` method." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area\n", "\n", " def __add__(self, other):\n", " return Room(f\"{self.name}_{other.name}\", self.area + other.area)\n", "\n", " def __str__(self):\n", " return f\"\"" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " \n" ] } ], "source": [ "small = Room(\"small\", 9)\n", "big = Room(\"big\", 21)\n", "print(small, big, small + big)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Would the order of how the rooms are added affect the final room? As they are added now, the name is determined by the order, but do we want that? Or would we prefer to have:\n", "```python\n", " small + big == big + small\n", "```\n", "That bring us to another operator, equal to: `==`. The method needed to produce such comparison is `__eq__`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area\n", "\n", " def __add__(self, other):\n", " return Room(f\"{self.name}_{other.name}\", self.area + other.area)\n", "\n", " def __eq__(self, other):\n", " return self.area == other.area and set(self.name.split(\"_\")) == set(\n", " other.name.split(\"_\")\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, in this way two rooms of the same area are \"equal\" if their names are composed by the same." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "small = Room(\"small\", 9)\n", "big = Room(\"big\", 21)\n", "large = Room(\"superbig\", 30)\n", "print(small + big == big + small)\n", "print(small + big == large)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can add the other comparisons to know which room is bigger or smaller with the following functions:\n", "\n", "| Operator | Function |\n", "|----|----|\n", "| `<` | `__lt__(self, other)` |\n", "| `<=` | `__le__(self, other)` |\n", "| `>` | `__gt__(self, other)`|\n", "| `>=` | `__ge__(self, other)` |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's add people to the rooms and check whether they are in one room or not." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area\n", " self.occupants = []\n", "\n", " def add_occupant(self, name):\n", " self.occupants.append(name)\n", "\n", "\n", "circus = Room(\"Circus\", 3)\n", "circus.add_occupant(\"Graham\")\n", "circus.add_occupant(\"Eric\")\n", "circus.add_occupant(\"Terry\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How do we know if John is in the room? We can check the `occupants` list:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"John\" in circus.occupants" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or making it more readable adding a membership definition:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Room:\n", " def __init__(self, name, area):\n", " self.name = name\n", " self.area = area\n", " self.occupants = []\n", "\n", " def add_occupant(self, name):\n", " self.occupants.append(name)\n", "\n", " def __contains__(self, value):\n", " return value in self.occupants\n", "\n", "\n", "circus = Room(\"Circus\", 3)\n", "circus.add_occupant(\"Graham\")\n", "circus.add_occupant(\"Eric\")\n", "circus.add_occupant(\"Terry\")\n", "\n", "\"Terry\" in circus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can add lots more operators to classes. For example, `__getitem__` to let you index or access part of your object like a sequence or dictionary, _e.g._, `newObject[1]` or `newObject[\"data\"]`, or `__len__` to return a number of elements in your object. Probably the most exciting\n", "one is `__call__`, which overrides the `()` operator; this allows us to define classes that *behave like functions*! We call these **callables**." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello Eric\n" ] } ], "source": [ "class Greeter:\n", " def __init__(self, greeting):\n", " self.greeting = greeting\n", "\n", " def __call__(self, name):\n", " print(self.greeting, name)\n", "\n", "\n", "greeter_instance = Greeter(\"Hello\")\n", "\n", "greeter_instance(\"Eric\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "We've now come full circle in the blurring of the distinction between functions and objects! The full power of functional programming is really remarkable.\n", "\n", "If you want to know more about the topics in this lecture, using a different\n", "language syntax, I recommend you watch the [Abelson and Sussman](https://www.youtube.com/watch?v=2Op3QLzMgSY)\n", "\"Structure and Interpretation of Computer Programs\" lectures. These are the Computer Science\n", "equivalent of the Feynman Lectures!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next [notebook](./08_05_operator_overloading_example.ipynb) shows a detailed example of how to apply operator overloading to create your own symbolic algebra system." ] } ], "metadata": { "jekyll": { "display_name": "Operator Overloading" }, "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.8.13" } }, "nbformat": 4, "nbformat_minor": 2 }