{ "cells": [ { "cell_type": "markdown", "id": "d9d9ed57", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## DS2500 Day 9\n", "\n", "Feb 10, 2023\n", "\n", "### Content:\n", "- oop: overloading operators\n", "- oop: class attributes & methods\n", "\n", "### Admin:\n", "- hw1 due Friday\n", "- \n", "\n" ] }, { "cell_type": "markdown", "id": "caa7527b", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Goal: Build an intuitive and convenient way of measuring time\n", "\n", "Student: take a minute to study the interface below. \n", "- What do you notice? \n", "- What questions do you have?\n", "\n", "`TimeDelta` is a measurement of time between two moments.\n", "\n", "\n", "```python\n", "# build a 'TimeDelta', representing a period of time\n", "x = TimeDelta(second=100)\n", "assert str(x) == 'TimeDelta(second=40, minute=1, hour=0)'\n", "\n", "# build another time delta\n", "y = TimeDelta(second=100, minute=70)\n", "assert str(y) == 'TimeDelta(second=40, minute=11, hour=1)'\n", "\n", "# notice: we can add our TimeDelta objects together\n", "assert str(x + y) == 'TimeDelta(second=20, minute=13, hour=1)'\n", "\n", "# notice: we can multiply our TimeDelta objects by ints / floats\n", "assert str(x * 100) == 'TimeDelta(second=40, minute=46, hour=2)'\n", "```\n" ] }, { "cell_type": "markdown", "id": "7d524286", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Mea Culpa: We're re-inventing the sundial\n", "To study operator overloading today lets pretend that python doesn't already have a [wonderful package to manage time](https://docs.python.org/3/library/datetime.html), so that we can build our own 'TimeDelta' object.\n" ] }, { "cell_type": "markdown", "id": "10b3ad37", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Helpful Reminder: floor division `//` and the modulus operator `%`\n", "\n", "Our `TimeDelta` converts all the chunks of 60 seconds from the input `seconds` into minutes automatically in the constructor:\n", "```python\n", "# build a 'TimeDelta', representing a period of time\n", "x = TimeDelta(second=100)\n", "assert str(x) == 'TimeDelta(second=40, minute=1, hour=0)'\n", "```\n", "\n", "How is this accomplished?\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "9410b8d2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.6666666666666667" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# normal division\n", "100 / 60\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "2aaa5eeb", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# notice: floor division takes the \"floor\" of the normal division\n", "# (i.e. rounds down to the nearest integer)\n", "100 // 60\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "56c0481f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "40" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# we can get the \"remainder\" of the division operation via the modulus operator\n", "# (i.e. what remains after dividing 100 by 60?)\n", "100 % 60\n" ] }, { "cell_type": "markdown", "id": "544ae3c3", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# In Class Activity A\n", "\n", "Build a TimeDelta object which passes the assert statements below. Be sure to properly document your class definition.\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "fb2fdba5", "metadata": {}, "outputs": [], "source": [ "class TimeDelta:\n", " \"\"\" a measurement of time between two moments\n", " \n", " Attributes:\n", " second (int): seconds between two moments (0 <= second < 60)\n", " minute (int): minutes between two moments (0 <= minute < 60)\n", " hour (int): hours between two moments \n", " \"\"\"\n", " def __init__(self, second=0, minute=0, hour=0):\n", " # compute true seconds\n", " self.second = second % 60\n", " \n", " # add leftover seconds to input minute\n", " minute = minute + second // 60\n", " \n", " # compute ture minutes\n", " self.minute = minute % 60\n", " \n", " # add leftover minutes to hour & store\n", " self.hour = hour + minute // 60\n", " \n", " \n", " def __repr__(self):\n", " return f'TimeDelta(second={self.second}, minute={self.minute}, hour={self.hour})'" ] }, { "cell_type": "code", "execution_count": 10, "id": "775dcfd6", "metadata": {}, "outputs": [], "source": [ "# test0\n", "x = TimeDelta(second=100)\n", "assert str(x) == 'TimeDelta(second=40, minute=1, hour=0)'" ] }, { "cell_type": "code", "execution_count": 11, "id": "40560056", "metadata": {}, "outputs": [], "source": [ "# test1\n", "y = TimeDelta(second=100, minute=70)\n", "assert str(y) == 'TimeDelta(second=40, minute=11, hour=1)'" ] }, { "cell_type": "code", "execution_count": 12, "id": "4cdb64a0", "metadata": {}, "outputs": [], "source": [ "# test2\n", "z = TimeDelta(second=3601)\n", "assert str(z) == 'TimeDelta(second=1, minute=0, hour=1)'" ] }, { "cell_type": "code", "execution_count": null, "id": "a6e55b0a", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b37d5b67", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "e9660422", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Operator Overloading\n", "\n", "**Operator Overloading** is the process of defining custom operation methods (e.g. `+`, `-`, `*`, `/`) for our objects.\n", "\n", "Per the given interface below, we see our target `TimeDelta` object has its own `+` and `*` methods:\n", "\n", "```python\n", "# build a few TimeDelta\n", "a = TimeDelta(second=1, minute=2, hour=3)\n", "b = TimeDelta(second=4, minute=5, hour=6)\n", "\n", "# notice: we can add our TimeDelta objects together\n", "assert str(a + b) == 'TimeDelta(second=5, minute=7, hour=9)'\n", "\n", "# notice: we can multiply our TimeDelta objects by ints / floats\n", "assert str(a * 2) == 'TimeDelta(second=2, minute=4, hour=6)'\n", "```\n" ] }, { "cell_type": "markdown", "id": "88a6f5d8", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# What is python really doing when it computes `a + b`?\n", "1. identify the method name associate with the operation:\n", " 1. [lookup table here](https://docs.python.org/3/library/operator.html#mapping-operators-to-functions)\n", " 1. append two leading and trailing underscores (e.g. `__add__`)\n", "\n", "1. search for the corresponding method in the leftward object\n", " - e.g. `a + b` is equivilent to `a.__add__(b)`\n", "\n", "#### (++) a few extras\n", "- If the method from step 2 above is not found (or raises a `NotImplementedError`), python searches for a \"rightward\" version of the operation from the object on the right\n", " - e.g. `a + b` results in `b.__radd__(a)`\n", "- Notice that defining each and every operation is a bit redundant, right?\n", " - i.e. do we really need a distinct operation for `>`, `>=`, `<`, `<=`, `==`?\n", " - no ... a \"complete\" subset will do\n" ] }, { "cell_type": "markdown", "id": "18f1ce23", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Operator Overloading (implementation)\n", "\n", "To define \"addition\" for our object, we must define the function `TimeDelta.__add__()`\n", "\n", "```python\n", " def __add__(self, other): \n", " # combine self and other into a new TimeDelta object (somehow)\n", " return new_time_delta_object\n", "```\n", "\n", "Notes:\n", "- other represents the object we're adding to TimeDelta\n", " - for addition, its only meaningful when `other` is another TimeDelta\n", " - for multiplication, its only meaningful when `other` is a float or int\n", "- the output of this function is the new time delta object\n", " - its often expected that an operation gives a new output and doesn't modify original inputs `self` and `other`\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "020042c6", "metadata": {}, "outputs": [], "source": [ "class TimeDelta:\n", " \"\"\" a measurement of time between two moments\n", " \n", " Attributes:\n", " second (int): seconds between two moments (0 <= second < 60)\n", " minute (int): minutes between two moments (0 <= minute < 60)\n", " hour (int): hours between two moments \n", " \"\"\"\n", " sec_per_min = 60\n", " \n", " def __mul__(self, other):\n", " \"\"\" scale a TimeDelta object by some constant\n", " \n", " Args:\n", " other (float): some scale to apply\n", " \"\"\"\n", " assert type(other) in (int, float), \\\n", " 'TimeDelta can only be multiplied by int or float'\n", " \n", " return TimeDelta(second=self.second * other,\n", " minute=self.minute * other,\n", " hour=self.hour * other)\n", "\n", " \n", " def __init__(self, second=0, minute=0, hour=0):\n", " # store seconds (after removing minutes)\n", " self.second = second % sec_per_min\n", " \n", " # add any extra minutes (from seconds >= 60)\n", " minute += second // sec_per_min\n", " \n", " # store minutes (after removing hours)\n", " self.minute = minute % 60\n", " \n", " # store hours, including any extra hours (from minutes >= 60)\n", " self.hour = hour + minute // 60\n", " \n", " def __repr__(self):\n", " return f'TimeDelta(second={self.second}, minute={self.minute}, hour={self.hour})'\n", " \n", " def __add__(self, other):\n", " \"\"\" sum time between two TimeDelta objects \n", " \n", " Args:\n", " other (TimeDelta): other TimeDelta\n", " \"\"\"\n", " assert isinstance(other, TimeDelta), \\\n", " 'TimeDelta can only be added to other TimeDelta'\n", " \n", " return TimeDelta(second=self.second + other.second,\n", " minute=self.minute + other.minute,\n", " hour=self.hour + other.hour)" ] }, { "cell_type": "code", "execution_count": 24, "id": "ceaeb2a9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'1.5.3'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "pd.__version__" ] }, { "cell_type": "code", "execution_count": 21, "id": "fa9a16ad", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for +: 'TimeDelta' and 'TimeDelta'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[21], line 6\u001b[0m\n\u001b[1;32m 3\u001b[0m b \u001b[38;5;241m=\u001b[39m TimeDelta(second\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m4\u001b[39m, minute\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m, hour\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m6\u001b[39m)\n\u001b[1;32m 5\u001b[0m \u001b[38;5;66;03m# notice: we can add our TimeDelta objects together\u001b[39;00m\n\u001b[0;32m----> 6\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(\u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m) \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mTimeDelta(second=5, minute=7, hour=9)\u001b[39m\u001b[38;5;124m'\u001b[39m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;66;03m# notice: we can multiply our TimeDelta objects by ints / floats\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mstr\u001b[39m(a \u001b[38;5;241m*\u001b[39m \u001b[38;5;241m2\u001b[39m) \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mTimeDelta(second=2, minute=4, hour=6)\u001b[39m\u001b[38;5;124m'\u001b[39m\n", "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'TimeDelta' and 'TimeDelta'" ] } ], "source": [ "# build a 'TimeDelta', representing a period of time\n", "a = TimeDelta(second=1, minute=2, hour=3)\n", "b = TimeDelta(second=4, minute=5, hour=6)\n", "\n", "# notice: we can add our TimeDelta objects together\n", "assert str(a + b) == 'TimeDelta(second=5, minute=7, hour=9)'\n", "\n", "# notice: we can multiply our TimeDelta objects by ints / floats\n", "assert str(a * 2) == 'TimeDelta(second=2, minute=4, hour=6)'\n" ] }, { "cell_type": "markdown", "id": "2fd48d81", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Advice:\n", "\n", "Overload an operator when the behavior can be unambiguously guessed:\n", "- e.g. adding two `pd.Series` objects together\n", "\n", "If behavior isn't obvious, it might be worth making a method with a real function name to cue the reader in:\n", "- e.g. `a.combine_with_another_obj_somehow(b)`\n", "\n", "#### Function names are a great opportunity to document your code! (don't miss the chance)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "c7ca34ed", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6e6ab81b", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "ea6da1f7", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "146c3310", "metadata": {}, "source": [ "# In Class Activity B\n", "\n", "Add a subtraction method to `TimeDelta` above so that it passes the asserts given below.\n", "\n", "**Hint:** I [wonder](https://docs.python.org/3/library/operator.html#mapping-operators-to-functions) what method name python looks for to do a subtraction operation? ... this is the one we should be building.\n", "\n", "(++) This might feel redundant, is there a way we could re-use existing operations to build subtraction?\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b2ee77ed", "metadata": {}, "outputs": [], "source": [ "class TimeDelta:\n", " \"\"\" a measurement of time between two moments\n", " \n", " Attributes:\n", " second (int): seconds between two moments (0 <= second < 60)\n", " minute (int): minutes between two moments (0 <= minute < 60)\n", " hour (int): hours between two moments \n", " \"\"\"\n", " def __init__(self, second=0, minute=0, hour=0):\n", " # store seconds (after removing minutes)\n", " self.second = second % 60\n", " \n", " # add any extra minutes (from seconds >= 60)\n", " minute += second // 60\n", " \n", " # store minutes (after removing hours)\n", " self.minute = minute % 60\n", " \n", " # store hours, including any extra hours (from minutes >= 60)\n", " self.hour = hour + minute // 60\n", " \n", " def __repr__(self):\n", " return f'TimeDelta(second={self.second}, minute={self.minute}, hour={self.hour})'\n", " \n", " def __add__(self, other):\n", " \"\"\" sum time between two TimeDelta objects \n", " \n", " Args:\n", " other (TimeDelta): other TimeDelta\n", " \"\"\"\n", " assert isinstance(other, TimeDelta), \\\n", " 'TimeDelta can only be added to other TimeDelta'\n", " \n", " return TimeDelta(second=self.second + other.second,\n", " minute=self.minute + other.minute,\n", " hour=self.hour + other.hour)\n", " \n", " def __mul__(self, other):\n", " \"\"\" scale a TimeDelta object by some constant\n", " \n", " Args:\n", " other (float): some scale to apply\n", " \"\"\"\n", " assert type(other) in (int, float), \\\n", " 'TimeDelta can only be multiplied by int or float'\n", " \n", " return TimeDelta(second=self.second * other,\n", " minute=self.minute * other,\n", " hour=self.hour * other)\n", " \n", " def __sub__(self, other):\n", " \"\"\" subtract time between two TimeDelta objects \n", " \n", " Args:\n", " other (TimeDelta): other TimeDelta\n", " \"\"\"\n", " assert isinstance(other, TimeDelta), \\\n", " 'TimeDelta can only be subtracted from other TimeDelta'\n", " \n", " return TimeDelta(second=self.second - other.second,\n", " minute=self.minute - other.minute,\n", " hour=self.hour - other.hour)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "13194963", "metadata": {}, "outputs": [], "source": [ "# build a 'TimeDelta', representing a period of time\n", "a = TimeDelta(second=1, minute=2, hour=3)\n", "b = TimeDelta(second=4, minute=5, hour=6)\n", "\n", "# notice: we can now subtract one timedelta from another\n", "assert str(b - a) == 'TimeDelta(second=3, minute=3, hour=3)'\n" ] }, { "cell_type": "markdown", "id": "295fdb87", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Class Methods & Class Attributes\n", "\n", "Remember:\n", "- **attributes** are data (variables) associated with an object\n", "- **methods** are functions associated with an object\n", "\n", "However, sometimes we want to associate a particular attribute or method to the class itself (i.e. all objects of a particular class).\n" ] }, { "cell_type": "markdown", "id": "fed21100", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Class Attributes\n", "\n", "We can assign attributes to an entire Class, rather than a single instance of the class (an object):\n" ] }, { "cell_type": "code", "execution_count": 39, "id": "e1c70b8a", "metadata": {}, "outputs": [], "source": [ "class SillyClass:\n", " # how_many is a class attribute\n", " # all objects of type SillyClass can access it, effectively sharing the same variable\n", " how_many = 0\n", " \n", " def __init__(self):\n", " # increment counter of how many silly class instances have been made\n", " SillyClass.how_many += 1\n" ] }, { "cell_type": "code", "execution_count": 40, "id": "c9f7882f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# you can access this variable via the class directly\n", "# (notice: there is no particular object in this cell ... though you can access that way too)\n", "SillyClass.how_many" ] }, { "cell_type": "code", "execution_count": 41, "id": "c957a292", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# notice: each constructor call __init__ refered to the same variable how_many\n", "silly_class0 = SillyClass()\n", "silly_class1 = SillyClass()\n", "silly_class2 = SillyClass()\n", "SillyClass.how_many\n" ] }, { "cell_type": "code", "execution_count": 44, "id": "a567655d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "silly_class0.how_many is SillyClass.how_many" ] }, { "cell_type": "code", "execution_count": 30, "id": "9b8e22f7", "metadata": {}, "outputs": [], "source": [ "# you can also access this attribute from any instance\n", "silly_class0.how_many += 100\n" ] }, { "cell_type": "code", "execution_count": null, "id": "f2a108ae", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "6d0bfce7", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## When should I use a class attribute?\n", "\n", "Use a class attribute when we want to store one value for all instances of the class because:\n", "- the value is relevant to the set of all instances (as above)\n", "- the value is constant across all instances\n" ] }, { "cell_type": "markdown", "id": "76bbd7d8", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "### Purely hypothetically speaking (on a purely hypothetical hw2 ...) \n", "You're tasked with building a `MonopolyPropertyHand` \n", "- tracks an individual players monopoly properties\n", "\n", "Where would you store information about how many properties of each group (e.g. Dark Purple, Light Blue, Purple, Orange) are required to obtain a monopoly in that group?\n", "- is the value relevant to a particular player's properties (attribute) or\n", "- is the value constant & relevant to all player's properties (class attribute)\n" ] }, { "cell_type": "markdown", "id": "89145e76", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Class Methods\n", "\n", "Some functions are better associated with an entire class, rather than a particular instance object.\n", "\n", "What if we wanted to add a method `.from_string()` which accepts a string to build a `TimeDelta` object?\n", "- inputs: `03:02:01` implies 3 hours, 2 minutes and 1 second\n", "- output: `TimeDelta(hour=3, minute=2, second=1)`\n", "- notice that this behavior isnt associated with any particular `TimeDelta` object -> class method\n" ] }, { "cell_type": "code", "execution_count": 45, "id": "400e06d8", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['03', '02', '01']" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# str.split() will split a string on a particular character\n", "'03:02:01'.split(':')" ] }, { "cell_type": "code", "execution_count": 46, "id": "c3f95373", "metadata": {}, "outputs": [], "source": [ "# unpacking works nicely here\n", "hour, minute, second = '03:02:01'.split(':')\n" ] }, { "cell_type": "code", "execution_count": 47, "id": "f18034b3", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'03'" ] }, "execution_count": 47, "metadata": {}, "output_type": "execute_result" } ], "source": [ "hour\n" ] }, { "cell_type": "code", "execution_count": 48, "id": "0e3823ed", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'02'" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "minute\n" ] }, { "cell_type": "code", "execution_count": 49, "id": "fbf84694", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'01'" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "second\n" ] }, { "cell_type": "code", "execution_count": 55, "id": "2e906449", "metadata": {}, "outputs": [], "source": [ "class TimeDelta2:\n", " \"\"\" a measurement of time between two moments\n", " \n", " Attributes:\n", " second (int): seconds between two moments (0 <= second < 60)\n", " minute (int): minutes between two moments (0 <= minute < 60)\n", " hour (int): hours between two moments \n", " \"\"\"\n", " def __init__(self, second=0, minute=0, hour=0):\n", " # store seconds (after removing minutes)\n", " self.second = second % 60\n", " \n", " # add any extra minutes (from seconds >= 60)\n", " minute += second // 60\n", " \n", " # store minutes (after removing hours)\n", " self.minute = minute % 60\n", " \n", " # store hours, including any extra hours (from minutes >= 60)\n", " self.hour = hour + minute // 60\n", " \n", " def __repr__(self):\n", " return f'TimeDelta2(second={self.second}, minute={self.minute}, hour={self.hour})'\n", " \n", " @classmethod\n", " def from_user(cls):\n", " \"\"\" builds a TimeDelta2 from a string of format HH:MM:SS\n", " \n", " Args:\n", " str_time (str): a string of format HH:MM:SS. we expect\n", " 3 numerical values joined by ':'\n", " \"\"\"\n", " hour = input('input hour')\n", " minute = input('input minute')\n", " sec = input('input sec')\n", " \n", " # cast each to floats before building TimeDelta2 object\n", " return TimeDelta2(second=float(sec), \n", " minute=float(minute), \n", " hour=float(hour))\n", " \n", " @classmethod\n", " def from_string(cls, str_time):\n", " \"\"\" builds a TimeDelta2 from a string of format HH:MM:SS\n", " \n", " Args:\n", " str_time (str): a string of format HH:MM:SS. we expect\n", " 3 numerical values joined by ':'\n", " \"\"\"\n", " print(f'What is the cls argument? {cls}')\n", " \n", " # split string into its hour, minute and second\n", " hour, minute, second = str_time.split(':')\n", " \n", " # cast each to floats before building TimeDelta2 object\n", " return TimeDelta2(second=float(second), \n", " minute=float(minute), \n", " hour=float(hour))\n" ] }, { "cell_type": "code", "execution_count": 56, "id": "abc3668e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "input hour10\n", "input minute4\n", "input sec123\n" ] }, { "data": { "text/plain": [ "TimeDelta2(second=3.0, minute=6.0, hour=10.0)" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "TimeDelta2.from_user()" ] }, { "cell_type": "code", "execution_count": 51, "id": "680bcff3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "What is the cls argument? \n" ] }, { "data": { "text/plain": [ "TimeDelta2(second=1.0, minute=2.0, hour=3.0)" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "TimeDelta2.from_string('03:02:01')\n" ] }, { "cell_type": "code", "execution_count": 52, "id": "202696f6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "TimeDelta2(second=0, minute=40, hour=1)" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "TimeDelta2(minute=100)" ] }, { "cell_type": "markdown", "id": "2ee6086b", "metadata": {}, "source": [ "### Syntax of Class Method vs an ordinary Method\n", "\n", "```python\n", " @classmethod\n", " def from_string(cls, str_time):\n", "```\n", "\n", "two differences:\n", "- it requires the `@classmethod` decorator\n", "- we use `cls` to indicate that the first argument is the class is `TimeDelta2`\n", " - we reserve `self` for a particular instance object\n", " - convention: dont name the 1st input to a classmethod self\n" ] }, { "cell_type": "markdown", "id": "90f1fc65", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# When should I use a class method?\n", "\n", "Use a Class Method when\n", "- function does not use a particular object's attributes to run\n", " - common use case: provide an \"alternate constructor\" to build objects from another convenient data format\n", " - e.g. TimeDelta objects with input `02:04:04` or similar example above\n" ] }, { "cell_type": "code", "execution_count": null, "id": "961381fa", "metadata": {}, "outputs": [], "source": [ "\n" ] }, { "cell_type": "markdown", "id": "8f8ccc35", "metadata": {}, "source": [ "# In Class Activity C\n", "\n", "Complete the `WordListWithStats` class definition below so that it passes the asserts which follow\n", "- `add_word()`\n", "- overload any operators used in the assert statement (`+` and `len()`)\n", "\n", "(++) We'd rather have two interfaces to build these objects:\n", "- passing `word_list` (in current `__init__()` method)\n", "- passing both `char_count` and `word_list`\n", "\n", "Is there some way to build an alternate constructor (hint: class method) which can support both interfaces? \n", "- Which interface should be the official `__init__()` while the other is `from_something()`? \n", "- study (and imitate) the class method in `TimeDelta2.from_string()`\n" ] }, { "cell_type": "code", "execution_count": 58, "id": "4f88d2fa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a\n", "s\n", "d\n", "f\n" ] } ], "source": [ "for c in 'asdf':b\n", " print(c)" ] }, { "cell_type": "code", "execution_count": 67, "id": "6c8e1f7b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['a', 'b', 'c', 'd']" ] }, "execution_count": 67, "metadata": {}, "output_type": "execute_result" } ], "source": [ "['a', 'b'] + ['c', 'd']" ] }, { "cell_type": "code", "execution_count": 68, "id": "b9ec4800", "metadata": {}, "outputs": [], "source": [ "from collections import defaultdict\n", "\n", "class WordListWithStats:\n", " \"\"\" manages a list of words and character count across words \n", " \n", " Attributes:\n", " word_list (list): a list of words\n", " char_count (dict): keys are characters, values are how many\n", " times character appears in all words in self.word_list\n", " \"\"\"\n", " def __init__(self, word_list=tuple()):\n", " # init empty attribues\n", " self.char_count = defaultdict(lambda: 0)\n", " self.word_list = list()\n", " \n", " for word in word_list:\n", " self.add_word(word)\n", " \n", " def add_word(self, word):\n", " \"\"\" adds a word into list, updates char_count \n", " \n", " Args:\n", " word (str): a word\n", " \"\"\"\n", " self.word_list.append(word)\n", " \n", " for c in word:\n", " self.char_count[c] += 1\n", " \n", " def __add__(self, other):\n", " \"\"\" adds two WordListWithStats together\n", " \n", " Args:\n", " other (WordListWithStats):\n", " \"\"\"\n", " return WordListWithStats(word_list=self.word_list + other.word_list)\n", " \n", " def rm_word(self, word):\n", " \"\"\" removes a word from list, updates char_count \n", " \n", " Args:\n", " word (str): a word\n", " \"\"\"\n", " self.word_list.remove(word) \n", " for c in word:\n", " self.char_count[c] -= 1\n", " \n", " if not self.char_count[c]:\n", " # delete key if value is 0\n", " del self.char_count[c]\n", " \n", " def __len__(self):\n", " \"\"\" how many words are in word_list\"\"\"\n", " return len(self.word_list)\n", " \n", " \n" ] }, { "cell_type": "code", "execution_count": 69, "id": "acab92c5", "metadata": {}, "outputs": [], "source": [ "day_tup = 'monday', 'tuesday', 'wednesday'\n", "day_list_with_stat = WordListWithStats(day_tup)\n", "assert day_list_with_stat.word_list == ['monday', 'tuesday', 'wednesday']\n", "\n", "day_list_with_stat.rm_word('wednesday')\n", "assert day_list_with_stat.word_list == ['monday', 'tuesday']\n", "assert dict(day_list_with_stat.char_count) == {'m': 1, 'o': 1, 'n': 1, 'd': 2, \n", " 'a': 2, 'y': 2, 't': 1, 'u': 1, \n", " 'e': 1, 's': 1}\n" ] }, { "cell_type": "code", "execution_count": 70, "id": "d760a48b", "metadata": {}, "outputs": [], "source": [ "beatles_tup = 'paul', 'george', 'ringo', 'john'\n", "beatles_list_with_stat = WordListWithStats(beatles_tup)\n", "sum_list_with_stat = beatles_list_with_stat.__add__(day_list_with_stat)\n", "assert sum_list_with_stat.word_list == ['paul', 'george', 'ringo', 'john', 'monday', 'tuesday']\n", "assert dict(sum_list_with_stat.char_count) == {'p': 1, 'a': 3, 'u': 2, 'l': 1, 'g': 3,\n", " 'e': 3, 'o': 4, 'r': 2, 'i': 1, 'n': 3,\n", " 'j': 1, 'h': 1, 'm': 1, 'd': 2, 'y': 2,\n", " 't': 1, 's': 1}\n" ] }, { "cell_type": "code", "execution_count": null, "id": "f3a32a35", "metadata": {}, "outputs": [], "source": [ "assert len(sum_list_with_stat) == 6\n" ] } ], "metadata": { "celltoolbar": "Slideshow", "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.10.6" } }, "nbformat": 4, "nbformat_minor": 5 }