{ "cells": [ { "cell_type": "markdown", "id": "adverse-hammer", "metadata": {}, "source": [ "# Lecture 9: Object-Oriented Programming (OOP)" ] }, { "cell_type": "markdown", "id": "contrary-mounting", "metadata": {}, "source": [ "OOP is a way of thinking about algorithms, and a way of structuring and organizing your code." ] }, { "cell_type": "markdown", "id": "altered-radiation", "metadata": {}, "source": [ "You define *objects*, and you give those objects *variable* and *functions*, which you then use as needed." ] }, { "cell_type": "markdown", "id": "secure-peeing", "metadata": {}, "source": [ "You already do this without knowing it! For example `list`s are objects. They have a variable which stores the things in the list. They have functions like `append` and `pop` that you use." ] }, { "cell_type": "code", "execution_count": 2, "id": "17a440aa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3, 4]\n" ] } ], "source": [ "L = [1, 2, 3]\n", "L.append(4)\n", "print(L)" ] }, { "cell_type": "markdown", "id": "known-gardening", "metadata": {}, "source": [ "Here's a simple example of a class:" ] }, { "cell_type": "code", "execution_count": 15, "id": "dense-financing", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " # two underscores _ _ init _ _\n", " def __init__(self, animal_name, animal_species, animal_age):\n", " self.name = animal_name\n", " self.species = animal_species\n", " self.age = animal_age\n", " self.fake = \"some value\"" ] }, { "cell_type": "code", "execution_count": 16, "id": "inside-storm", "metadata": {}, "outputs": [], "source": [ "hermes = Pet(\"Hermes\", \"cat\", 14)" ] }, { "cell_type": "code", "execution_count": 5, "id": "ec26ba07", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Pet object at 0x103b10690>\n" ] } ], "source": [ "print(hermes)" ] }, { "cell_type": "code", "execution_count": 17, "id": "5539dd69", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'some value'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes.fake" ] }, { "cell_type": "code", "execution_count": 12, "id": "miniature-planner", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hermes'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes.name" ] }, { "cell_type": "code", "execution_count": 13, "id": "desperate-innocent", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'cat'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes.species" ] }, { "cell_type": "code", "execution_count": 14, "id": "worth-colombia", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "14" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes.age" ] }, { "cell_type": "code", "execution_count": 18, "id": "departmental-dublin", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.Pet" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(hermes)" ] }, { "cell_type": "markdown", "id": "24c32124", "metadata": {}, "source": [ "print(hermes)" ] }, { "cell_type": "markdown", "id": "740a6ef7", "metadata": {}, "source": [ "\"dunder methods\" - \"double underscore\"" ] }, { "cell_type": "markdown", "id": "dressed-tutorial", "metadata": {}, "source": [ "Every class need a `__init__` function (that is two underscores on each side). This function is called automatically when you create a new *instance* of an object. (`hermes` is an *instance* of the class `Pet`.)" ] }, { "cell_type": "markdown", "id": "confirmed-promise", "metadata": {}, "source": [ "The first parameter to `__init__` in its definition always has to be `self`, which is how an object refers to itself. But when you're creating an instance later, you don't pass in a value for `self` -- it's automatically added." ] }, { "cell_type": "markdown", "id": "regulation-deadline", "metadata": {}, "source": [ "In our `Pet` class, we take the three inputs, `name`, `species`, and `age`, and we assign those to *class variables* `self.name`, `self.species`, and `self.age` so they are remembered by the object." ] }, { "cell_type": "markdown", "id": "7f399101", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "id": "31c35356", "metadata": {}, "source": [ "Representing things with classes makes it easier to keep track of the meaning of different variables." ] }, { "cell_type": "code", "execution_count": 19, "id": "therapeutic-polls", "metadata": {}, "outputs": [], "source": [ "class Job:\n", " \n", " def __init__(self, index, duration, deadline, profit):\n", " self.index = index\n", " self.duration = duration\n", " self.deadline = deadline\n", " self.profit = profit" ] }, { "cell_type": "code", "execution_count": 20, "id": "excessive-dictionary", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "j = [2,1,5]\n", "print(j[0])\n", "J = Job(1, 2, 1, 5)" ] }, { "cell_type": "code", "execution_count": 21, "id": "introductory-pharmacology", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "J.deadline" ] }, { "cell_type": "code", "execution_count": 22, "id": "norwegian-exposure", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "J.profit" ] }, { "cell_type": "code", "execution_count": 23, "id": "indonesian-abortion", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "J.duration" ] }, { "cell_type": "markdown", "id": "boxed-catch", "metadata": {}, "source": [ "---" ] }, { "cell_type": "code", "execution_count": 25, "id": "e7a7a9db", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The sum of 1 and 3 is 4.\n" ] } ], "source": [ "# f-string example\n", "x = 1\n", "y = 3\n", "z = x + y\n", "print(f\"The sum of {x} and {y} is {z}.\")" ] }, { "cell_type": "markdown", "id": "endangered-station", "metadata": {}, "source": [ "Now let's define some functions in the `Pet` class." ] }, { "cell_type": "code", "execution_count": 26, "id": "failing-investment", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " # f-string\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif self.species == \"dog\":\n", " return 11 * self.age\n", " elif self.species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None" ] }, { "cell_type": "code", "execution_count": 27, "id": "bound-growth", "metadata": {}, "outputs": [], "source": [ "hermes = Pet(\"Hermes\", \"cat\", 14, \"meow\")" ] }, { "cell_type": "code", "execution_count": 28, "id": "reasonable-transformation", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes says meow.\n" ] } ], "source": [ "hermes.speak()" ] }, { "cell_type": "code", "execution_count": 29, "id": "alpine-needle", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 14.\n" ] } ], "source": [ "hermes.print_info()" ] }, { "cell_type": "code", "execution_count": 30, "id": "challenging-polish", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "98" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes.age_in_human_years()" ] }, { "cell_type": "code", "execution_count": 31, "id": "beginning-exhaust", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Pet object at 0x103fbe9d0>\n" ] } ], "source": [ "print(hermes)" ] }, { "cell_type": "markdown", "id": "pharmaceutical-restaurant", "metadata": {}, "source": [ "It would be better, especially for debugging, if we could print the object and have it show us useful information." ] }, { "cell_type": "markdown", "id": "rapid-practitioner", "metadata": {}, "source": [ "This is where the Python magic comes in. In addition to defining class functions we want to be able to call, we can also define some \"dunder methods\" that automatically change some behavior of the objects." ] }, { "cell_type": "markdown", "id": "searching-improvement", "metadata": {}, "source": [ "They called \"dunder methods\" because their names start and end with double underscores." ] }, { "cell_type": "markdown", "id": "external-impossible", "metadata": {}, "source": [ "The first one we'll see is `__str__`. Whenever you try to print an object or get it's string representation, it secretly calls `obj.__str__()` in the background." ] }, { "cell_type": "code", "execution_count": 32, "id": "tough-layer", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif species == \"dog\":\n", " return 11 * self.age\n", " elif species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None\n", " \n", " def __str__(self):\n", " # return \"this would be a string\"\n", " return f\"{self.name} / {self.species} / {self.age}\"" ] }, { "cell_type": "code", "execution_count": 33, "id": "martial-airport", "metadata": {}, "outputs": [], "source": [ "hermes = Pet(\"Hermes\", \"cat\", 14, \"meow\")" ] }, { "cell_type": "code", "execution_count": 34, "id": "least-meter", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes / cat / 14\n" ] } ], "source": [ "print(hermes)" ] }, { "cell_type": "code", "execution_count": 35, "id": "subsequent-eleven", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Hermes / cat / 14'" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "str(hermes)" ] }, { "cell_type": "markdown", "id": "center-praise", "metadata": {}, "source": [ "A closely related dunder method is `__repr__`, which tells Python how to show the object when you just use it's name (without `print`)" ] }, { "cell_type": "code", "execution_count": 37, "id": "pregnant-brighton", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif species == \"dog\":\n", " return 11 * self.age\n", " elif species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None\n", " \n", " def __str__(self):\n", " return f\"{self.name} / {self.species} / {self.age}\"\n", " \n", " def __repr__(self):\n", " return f\"Pet('{self.name}', '{self.species}', {self.age}, '{self.noise}')\"" ] }, { "cell_type": "code", "execution_count": 38, "id": "electrical-somewhere", "metadata": {}, "outputs": [], "source": [ "hermes = Pet(\"Hermes\", \"cat\", 14, \"meow\")" ] }, { "cell_type": "code", "execution_count": 39, "id": "intense-transport", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes / cat / 14\n" ] } ], "source": [ "print(hermes)" ] }, { "cell_type": "code", "execution_count": 40, "id": "acute-hungarian", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Pet('Hermes', 'cat', 14, 'meow')" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes" ] }, { "cell_type": "code", "execution_count": 41, "id": "conditional-electron", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 14.\n" ] } ], "source": [ "new_hermes = Pet('Hermes', 'cat', 14, 'meow')\n", "new_hermes.print_info()" ] }, { "cell_type": "markdown", "id": "starting-adjustment", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "id": "protected-genetics", "metadata": {}, "source": [ "One very important thing to keep in mind is that, by default, two objects are equal (==) only if they are literally the same object at the same memory location." ] }, { "cell_type": "code", "execution_count": 42, "id": "threaded-sally", "metadata": {}, "outputs": [], "source": [ "hermes1 = Pet(\"Hermes\", \"cat\", 14, \"meow\")\n", "hermes2 = Pet(\"Hermes\", \"cat\", 14, \"meow\")" ] }, { "cell_type": "code", "execution_count": 43, "id": "innocent-revision", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes1 == hermes2" ] }, { "cell_type": "code", "execution_count": 44, "id": "atmospheric-width", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4362165648" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(hermes1)" ] }, { "cell_type": "code", "execution_count": 45, "id": "vulnerable-nebraska", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4362167440" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(hermes2)" ] }, { "cell_type": "code", "execution_count": 46, "id": "adjusted-configuration", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes1 == hermes1" ] }, { "cell_type": "code", "execution_count": 47, "id": "bac2d50c", "metadata": {}, "outputs": [], "source": [ "third_hermes = hermes1" ] }, { "cell_type": "code", "execution_count": 48, "id": "dddf1eb2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4362165648" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(hermes1)" ] }, { "cell_type": "code", "execution_count": 49, "id": "bdf5f77c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4362165648" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(third_hermes)" ] }, { "cell_type": "code", "execution_count": 50, "id": "4105a43f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes1 == third_hermes" ] }, { "cell_type": "code", "execution_count": 51, "id": "94542d05", "metadata": {}, "outputs": [], "source": [ "from copy import deepcopy" ] }, { "cell_type": "code", "execution_count": 52, "id": "a150dace", "metadata": {}, "outputs": [], "source": [ "hermes4 = deepcopy(hermes1)" ] }, { "cell_type": "code", "execution_count": 53, "id": "da74797d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 14.\n" ] } ], "source": [ "hermes4.print_info()" ] }, { "cell_type": "code", "execution_count": 54, "id": "8c2c49de", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes4 == hermes1" ] }, { "cell_type": "code", "execution_count": 55, "id": "4456c04f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 14.\n" ] } ], "source": [ "hermes1.print_info()" ] }, { "cell_type": "code", "execution_count": 56, "id": "e50ef4c3", "metadata": {}, "outputs": [], "source": [ "hermes1.age = 20" ] }, { "cell_type": "code", "execution_count": 57, "id": "315c7638", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 20.\n" ] } ], "source": [ "hermes1.print_info()" ] }, { "cell_type": "code", "execution_count": 58, "id": "1b920fc5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hermes is a cat whose age is 20.\n" ] } ], "source": [ "third_hermes.print_info()" ] }, { "cell_type": "code", "execution_count": null, "id": "8e5143d7", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "responsible-terminal", "metadata": {}, "source": [ "You can change this with the `__eq__` dunder method, which redefines when two objects are equal, but you should consider whether you **should**. If you are writing patient management software for a veterinary clinic, do you want to consider two animals to be the same animal if they have the same name / species / age? Probably not!" ] }, { "cell_type": "code", "execution_count": 59, "id": "finished-mystery", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif species == \"dog\":\n", " return 11 * self.age\n", " elif species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None\n", " \n", " def __str__(self):\n", " return f\"{self.name} / {self.species} / {self.age}\"\n", " \n", " \n", " def __repr__(self):\n", " return f\"Pet('{self.name}', '{self.species}', {self.age}, '{self.noise}')\"\n", " \n", " def __eq__(self, other):\n", " \"\"\"\n", " return True if [self] and [other] have identical names, species, and ages\n", " \"\"\"\n", " return self.name == other.name and self.species == other.species and self.age == other.age" ] }, { "cell_type": "code", "execution_count": 60, "id": "colored-caution", "metadata": {}, "outputs": [], "source": [ "hermes1 = Pet(\"Hermes\", \"cat\", 14, \"meow\")\n", "hermes2 = Pet(\"Hermes\", \"cat\", 14, \"meow\")" ] }, { "cell_type": "code", "execution_count": 62, "id": "functional-company", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4362080016\n", "4362080208\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 62, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(id(hermes1))\n", "print(id(hermes2))\n", "hermes1 == hermes2" ] }, { "cell_type": "code", "execution_count": 63, "id": "equipped-screen", "metadata": {}, "outputs": [], "source": [ "hermes1 = Pet(\"Hermes\", \"cat\", 14, \"meow\")\n", "hermes2 = Pet(\"Hermes\", \"cat\", 14, \"growl\")" ] }, { "cell_type": "code", "execution_count": 64, "id": "enormous-taylor", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hermes1 == hermes2" ] }, { "cell_type": "markdown", "id": "stock-compound", "metadata": {}, "source": [ "One last dunder method for now: if you want to be able to compare two objects with `<` and `>`, define `__lt__`." ] }, { "cell_type": "code", "execution_count": 65, "id": "e6f8e879", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'<' not supported between instances of 'Pet' and 'Pet'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[65], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mhermes1\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m<\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mhermes2\u001b[49m\n", "\u001b[0;31mTypeError\u001b[0m: '<' not supported between instances of 'Pet' and 'Pet'" ] } ], "source": [ "hermes1 < hermes2" ] }, { "cell_type": "code", "execution_count": 73, "id": "noble-prison", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif species == \"dog\":\n", " return 11 * self.age\n", " elif species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None\n", " \n", " def __str__(self):\n", " return f\"{self.name} / {self.species} / {self.age}\"\n", " \n", " def __repr__(self):\n", " return f\"Pet('{self.name}', '{self.species}', {self.age}, '{self.noise}')\"\n", " \n", " def __eq__(self, other):\n", " \"\"\"\n", " return True if [self] and [other] have identical names, species, and ages\n", " \"\"\"\n", " return self.name == other.name and self.species == other.species and self.age == other.age\n", " \n", " def __lt__(self, other):\n", " \"\"\"\n", " return True if the age of self is less than the age of other\n", " \"\"\"\n", " return self.age < other.age" ] }, { "cell_type": "code", "execution_count": 74, "id": "peaceful-pantyhose", "metadata": {}, "outputs": [], "source": [ "animals = [\n", " Pet(\"Hermes\", \"cat\", 14, \"meow\"),\n", " Pet(\"Leopold\", \"cat\", 12, \"growl\"),\n", " Pet(\"Vaughn\", \"dog\", 11, \"woof\"),\n", " Pet(\"Malcolm\", \"cat\", 10, \"wheeze\")\n", "]" ] }, { "cell_type": "code", "execution_count": 75, "id": "6b73b717", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Hermes', 'cat', 14, 'meow'),\n", " Pet('Leopold', 'cat', 12, 'growl'),\n", " Pet('Vaughn', 'dog', 11, 'woof'),\n", " Pet('Malcolm', 'cat', 10, 'wheeze')]" ] }, "execution_count": 75, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animals" ] }, { "cell_type": "code", "execution_count": 76, "id": "concrete-friendly", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Malcolm', 'cat', 10, 'wheeze'),\n", " Pet('Vaughn', 'dog', 11, 'woof'),\n", " Pet('Leopold', 'cat', 12, 'growl'),\n", " Pet('Hermes', 'cat', 14, 'meow')]" ] }, "execution_count": 76, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted(animals)" ] }, { "cell_type": "code", "execution_count": 77, "id": "488a0af8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Hermes', 'cat', 14, 'meow'),\n", " Pet('Leopold', 'cat', 12, 'growl'),\n", " Pet('Vaughn', 'dog', 11, 'woof'),\n", " Pet('Malcolm', 'cat', 10, 'wheeze')]" ] }, "execution_count": 77, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animals" ] }, { "cell_type": "code", "execution_count": 78, "id": "1452c4be", "metadata": {}, "outputs": [], "source": [ "animals.sort()" ] }, { "cell_type": "code", "execution_count": 79, "id": "e1868857", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Malcolm', 'cat', 10, 'wheeze'),\n", " Pet('Vaughn', 'dog', 11, 'woof'),\n", " Pet('Leopold', 'cat', 12, 'growl'),\n", " Pet('Hermes', 'cat', 14, 'meow')]" ] }, "execution_count": 79, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animals" ] }, { "cell_type": "code", "execution_count": 80, "id": "3df2a8b1", "metadata": {}, "outputs": [], "source": [ "animals.sort(key=lambda pet: pet.name)" ] }, { "cell_type": "code", "execution_count": 81, "id": "b4b94828", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Hermes', 'cat', 14, 'meow'),\n", " Pet('Leopold', 'cat', 12, 'growl'),\n", " Pet('Malcolm', 'cat', 10, 'wheeze'),\n", " Pet('Vaughn', 'dog', 11, 'woof')]" ] }, "execution_count": 81, "metadata": {}, "output_type": "execute_result" } ], "source": [ "animals" ] }, { "cell_type": "code", "execution_count": null, "id": "7eb3ae11", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "assigned-stationery", "metadata": {}, "source": [ "---" ] }, { "cell_type": "markdown", "id": "announced-negative", "metadata": {}, "source": [ "Let's do one more example from scratch." ] }, { "cell_type": "code", "execution_count": 82, "id": "finnish-parker", "metadata": {}, "outputs": [], "source": [ "class Rectangle:\n", " \n", " def __init__(self, h, w):\n", " self.height = h\n", " self.width = w\n", " \n", " def perimeter(self):\n", " return 2 * self.height + 2 * self.width\n", " \n", " def area(self):\n", " return self.height * self.width\n", " \n", " def double_dimensions(self):\n", " return Rectangle(2 * self.height, 2 * self.width)\n", " \n", " def __eq__(self, other):\n", " return self.height == other.height and self.width == other.width\n", " \n", " def __lt__(self, other):\n", " return self.area() < other.area()\n", " \n", " def __str__(self):\n", " top = \"o\" + (\"-\" * self.width) + \"o\\n\"\n", " side = \"|\" + (\" \" * self.width) + \"|\\n\"\n", " return top + (side * self.height) + top\n", " \n", " def __repr__(self):\n", " return f\"Rectangle({self.height}, {self.width})\"" ] }, { "cell_type": "code", "execution_count": 83, "id": "least-passing", "metadata": {}, "outputs": [], "source": [ "R = Rectangle(3,4)" ] }, { "cell_type": "code", "execution_count": 84, "id": "interim-thesis", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Rectangle(3, 4)" ] }, "execution_count": 84, "metadata": {}, "output_type": "execute_result" } ], "source": [ "R" ] }, { "cell_type": "code", "execution_count": 85, "id": "collaborative-entrance", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "o----o\n", "| |\n", "| |\n", "| |\n", "o----o\n", "\n" ] } ], "source": [ "print(R)" ] }, { "cell_type": "code", "execution_count": 86, "id": "171d1cde", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Rectangle(6, 8)" ] }, "execution_count": 86, "metadata": {}, "output_type": "execute_result" } ], "source": [ "R.double_dimensions()" ] }, { "cell_type": "code", "execution_count": 87, "id": "experienced-missouri", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "o--------o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o--------o\n", "\n" ] } ], "source": [ "print(R.double_dimensions())" ] }, { "cell_type": "code", "execution_count": 88, "id": "framed-constitution", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "o----------------o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o----------------o\n", "\n" ] } ], "source": [ "print(R.double_dimensions().double_dimensions())" ] }, { "cell_type": "code", "execution_count": 89, "id": "southern-standard", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "12" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "R.area()" ] }, { "cell_type": "code", "execution_count": 90, "id": "lesbian-phoenix", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "14" ] }, "execution_count": 90, "metadata": {}, "output_type": "execute_result" } ], "source": [ "R.perimeter()" ] }, { "cell_type": "code", "execution_count": 91, "id": "breathing-native", "metadata": {}, "outputs": [], "source": [ "import random\n", "random_rectangles = [Rectangle(random.randint(1,6), random.randint(1,6)) for i in range(10)]" ] }, { "cell_type": "code", "execution_count": 92, "id": "atlantic-description", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Rectangle(3, 5),\n", " Rectangle(4, 3),\n", " Rectangle(5, 1),\n", " Rectangle(5, 2),\n", " Rectangle(2, 5),\n", " Rectangle(3, 1),\n", " Rectangle(6, 2),\n", " Rectangle(1, 4),\n", " Rectangle(3, 6),\n", " Rectangle(5, 6)]" ] }, "execution_count": 92, "metadata": {}, "output_type": "execute_result" } ], "source": [ "random_rectangles" ] }, { "cell_type": "code", "execution_count": 93, "id": "prostate-november", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "o-----o\n", "| |\n", "| |\n", "| |\n", "o-----o\n", "\n", "o---o\n", "| |\n", "| |\n", "| |\n", "| |\n", "o---o\n", "\n", "o-o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o-o\n", "\n", "o--o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o--o\n", "\n", "o-----o\n", "| |\n", "| |\n", "o-----o\n", "\n", "o-o\n", "| |\n", "| |\n", "| |\n", "o-o\n", "\n", "o--o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o--o\n", "\n", "o----o\n", "| |\n", "o----o\n", "\n", "o------o\n", "| |\n", "| |\n", "| |\n", "o------o\n", "\n", "o------o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o------o\n", "\n" ] } ], "source": [ "for R in random_rectangles:\n", " print(R)" ] }, { "cell_type": "code", "execution_count": 98, "id": "addressed-potato", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "o-o\n", "| |\n", "| |\n", "| |\n", "o-o\n", "\n", "(8, 3)\n", "o----o\n", "| |\n", "o----o\n", "\n", "(10, 4)\n", "o-o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o-o\n", "\n", "(12, 5)\n", "o--o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o--o\n", "\n", "(14, 10)\n", "o-----o\n", "| |\n", "| |\n", "o-----o\n", "\n", "(14, 10)\n", "o---o\n", "| |\n", "| |\n", "| |\n", "| |\n", "o---o\n", "\n", "(14, 12)\n", "o--o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o--o\n", "\n", "(16, 12)\n", "o-----o\n", "| |\n", "| |\n", "| |\n", "o-----o\n", "\n", "(16, 15)\n", "o------o\n", "| |\n", "| |\n", "| |\n", "o------o\n", "\n", "(18, 18)\n", "o------o\n", "| |\n", "| |\n", "| |\n", "| |\n", "| |\n", "o------o\n", "\n", "(22, 30)\n" ] } ], "source": [ "for R in sorted(random_rectangles, key=lambda rect: (rect.perimeter(), rect.area())):\n", " print(R)\n", " print((R.perimeter(), R.area()))" ] }, { "cell_type": "code", "execution_count": null, "id": "bac104c9", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "fc1996cb", "metadata": {}, "source": [ "#### Advanced Topic: Hashability (how to make objects that can be put in sets)" ] }, { "cell_type": "markdown", "id": "db0dd02e", "metadata": {}, "source": [ "(See me if you have any questions!)" ] }, { "cell_type": "code", "execution_count": 99, "id": "vertical-chorus", "metadata": {}, "outputs": [], "source": [ "third_hermes = hermes1" ] }, { "cell_type": "code", "execution_count": 100, "id": "retired-algebra", "metadata": {}, "outputs": [], "source": [ "L = [hermes1, third_hermes]" ] }, { "cell_type": "code", "execution_count": 101, "id": "fb8520b5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Pet('Hermes', 'cat', 14, 'meow'), Pet('Hermes', 'cat', 14, 'meow')]" ] }, "execution_count": 101, "metadata": {}, "output_type": "execute_result" } ], "source": [ "L" ] }, { "cell_type": "code", "execution_count": 102, "id": "502b0f4d", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'Pet'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[102], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;43mset\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mL\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'Pet'" ] } ], "source": [ "set(L)" ] }, { "cell_type": "code", "execution_count": 103, "id": "540f70d9", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'list'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[103], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;43mhash\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'list'" ] } ], "source": [ "hash([1,2,3])" ] }, { "cell_type": "code", "execution_count": 104, "id": "71d4cb24", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "529344067295497451" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash((1,2,3))" ] }, { "cell_type": "code", "execution_count": 105, "id": "da1bdd99", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unhashable type: 'Pet'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[105], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;43mhash\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mhermes1\u001b[49m\u001b[43m)\u001b[49m\n", "\u001b[0;31mTypeError\u001b[0m: unhashable type: 'Pet'" ] } ], "source": [ "hash(hermes1)" ] }, { "cell_type": "code", "execution_count": 106, "id": "3b2a4528", "metadata": {}, "outputs": [], "source": [ "class Pet:\n", " \n", " def __init__(self, name, species, age, noise):\n", " self.name = name\n", " self.species = species\n", " self.age = age\n", " self.noise = noise\n", " \n", " def speak(self):\n", " string = \"\"\n", " string += self.name\n", " string += \" says \"\n", " string += self.noise\n", " string += \".\"\n", " print(string)\n", " \n", " def print_info(self):\n", " string = f\"{self.name} is a {self.species} whose age is {self.age}.\"\n", " print(string)\n", " \n", " def age_in_human_years(self):\n", " \n", " # \"species\" would be a variable that is local to this function\n", " # \"self.species\" refers to the \"species\" variable stored by the\n", " # whole object\n", " if self.species == \"cat\":\n", " return 7 * self.age\n", " elif species == \"dog\":\n", " return 11 * self.age\n", " elif species == \"turtle\":\n", " return 4 * self.age\n", " else:\n", " return None\n", " \n", " def __str__(self):\n", " return f\"{self.name} / {self.species} / {self.age}\"\n", " \n", " def __repr__(self):\n", " return f\"Pet('{self.name}', '{self.species}', {self.age}, '{self.noise}')\"\n", " \n", " def __eq__(self, other):\n", " \"\"\"\n", " return True if [self] and [other] have identical names, species, and ages\n", " \"\"\"\n", " return self.name == other.name and self.species == other.species and self.age == other.age\n", " \n", " def __lt__(self, other):\n", " \"\"\"\n", " return True if the age of self is less than the age of other\n", " \"\"\"\n", " return self.age < other.age\n", " \n", " def __hash__(self):\n", " return hash((self.name, self.species, self.age, self.noise))" ] }, { "cell_type": "code", "execution_count": 107, "id": "8832af6d", "metadata": {}, "outputs": [], "source": [ "hermes1 = Pet(\"Hermes\", \"cat\", 13, \"meow\")\n", "hermes2 = hermes1" ] }, { "cell_type": "code", "execution_count": 108, "id": "1d4bfc73", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "-8277984128765011415" ] }, "execution_count": 108, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hash(hermes1)" ] }, { "cell_type": "code", "execution_count": 109, "id": "e4f71446", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{Pet('Hermes', 'cat', 13, 'meow')}" ] }, "execution_count": 109, "metadata": {}, "output_type": "execute_result" } ], "source": [ "{hermes1, hermes2}" ] }, { "cell_type": "code", "execution_count": null, "id": "ad863328", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "ae612a7c", "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.11.1" } }, "nbformat": 4, "nbformat_minor": 5 }