{ "cells": [ { "cell_type": "markdown", "id": "5a10bbac", "metadata": {}, "source": [ "[Home](Home.ipynb)\n", "\n", "# Coding Through Storytelling\n", "\n", "Contrary to what some may believe, programmers are not taught to discourage their imaginations, as if the ability to fantisize were a professional liability. \n", "\n", "Actually, what anchors programming to the real world are what we call \"use cases\" and these are stories we think through and glean from. \n", "\n", "Even before we start a coding project, we imagine the real world circumstances in which our work is put to some use. \"How will this app make someone's life better?\" is not a question to ask too late in the process.\n", "\n", "We may also invent short stories to help anchor our understanding of source code. We need to see the forest, the design pattern, without letting the trees (individual examples) dominate the foreground. For example, below are our example Supermarket, Shopper and Inventory classes. \n", "\n", "What stories might we weave through here?\n", "\n", "Here's one:\n", "\n", "A shopper wanders through a supermarket, pushing a shopping cart, and removing items from the shelves. The shopper comes in with an electronic wallet, a wifi device, such that pulling an item from the shelf completes a transaction. If the wallet has become empty or has insufficient funds, just put the item back on the shelf, while making no change to the wallet total. The shopper may still have enough left in the budget for something else.\n", "\n", "In eyeballing the code, we find the logic of try / except / else / finally corresponds to the story. We may choose to add more detail. We may express our story in the form of a simulation, or workout, or rehearsal, of the script." ] }, { "cell_type": "code", "execution_count": 1, "id": "ac1fdc04", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Loading inventory...\n", "Kirby: 970.06; {'Snicker-Snacks': 2, \"Polly's Peanuts\": 2, 'Dr. Soap': 2}\n", "{'Snicker-Snacks': [5.99, 8], \"Polly's Peanuts\": [3.99, 8], 'Dr. Soap': [4.99, 8]}\n", "SuperMarket with cash: 29.94\n", "Saving inventory...\n" ] } ], "source": [ "# %load shopping_v4.py\n", "\"\"\"\n", "Created on Fri Sep 30 10:10:41 2016\n", "\n", "@author: Kirby Urner\n", "\n", "Show some types working together to simulate a shopper\n", "in a Supermarket, with a fixed starting amount of money.\n", "\n", "Inventory keeps track of how much of a product remains.\n", "\n", "Supermarket keeps track of income, should equal sum of purchases.\n", "\"\"\"\n", "\n", "import json\n", "\n", "class NoMoney(Exception):\n", " pass\n", "\n", "class OutOfStock(Exception):\n", " pass\n", "\n", "class SuperMarket:\n", " \"\"\"\n", " Persists buyable items in a json file.\n", " Initializes with 0 cash\n", " \"\"\"\n", " def __init__(self, source):\n", " self.source = source\n", " \n", " def __enter__(self):\n", " self.inventory = Inventory(self.source)\n", " self.cash = 0\n", " return self\n", " \n", " def buy(self, shopper, item, how_many):\n", " \"\"\"\n", " remove money from shopper wallet, add qty of item\n", " to basket, abort if customer short on cash\n", " \"\"\"\n", " if item in self.inventory.wares: # check keys\n", " price = self.inventory.wares[item][0]\n", " try:\n", " self.inventory.remove_item(item, how_many)\n", " shopper.add_item(item, price, how_many)\n", " self.cash += price * how_many\n", " except NoMoney:\n", " # print(\"Customer out of money\")\n", " raise # re-raise exception\n", " except OutOfStock:\n", " # print(\"Don't have enough in stock\")\n", " raise\n", "\n", " def __exit__(self, *oops):\n", " \"\"\"\n", " write json file\n", " \"\"\"\n", " self.inventory.save_items()\n", " \n", " def __repr__(self):\n", " return \"SuperMarket with cash: {}\".format(self.cash)\n", " \n", "class Shopper:\n", " \n", " def __init__(self, name, budget):\n", " # self.pronoun = \"\"\n", " self.name = name\n", " self.basket = { }\n", " self.wallet = budget # budgeted allowance\n", "\n", " def add_item(self, item, price, qty):\n", " \"\"\"\n", " add qty of item to basket and pay, if money available\n", " \"\"\"\n", " if self.wallet - qty * price < 0:\n", " raise NoMoney\n", " self.basket[item] = self.basket.get(item, 0) + qty\n", " self.wallet -= qty * price\n", " \n", " def __repr__(self):\n", " return \"{}: {}; {}\".format(self.name, self.wallet, self.basket)\n", " \n", "class Inventory:\n", " \"\"\"\n", " Supermarket brings inventory instance on board upon\n", " initialization, increments / decrements items, reads\n", " and writes to json file. Does not track cash.\n", " \"\"\"\n", " \n", " def __init__(self, the_file):\n", " print(\"Loading inventory...\")\n", " self.storage = the_file\n", " with open(the_file, 'r') as warehouse:\n", " self.wares = json.load(warehouse)\n", " \n", " def save_items(self): \n", " print(\"Saving inventory...\")\n", " with open(self.storage, 'w') as warehouse:\n", " json.dump(self.wares, warehouse)\n", " \n", " def remove_item(self, item, qty):\n", " if qty > self.wares[item][1]:\n", " raise OutOfStock\n", " self.wares[item][1] -= qty\n", " \n", " def add_item(self, item, qty):\n", " self.wares[item][1] += qty\n", "\n", "def test_data():\n", " stuff = {\n", " \"Snicker-Snacks\": [5.99, 10],\n", " \"Polly's Peanuts\": [3.99, 10],\n", " \"Dr. Soap\": [4.99, 10]}\n", " with open(\"the_stuff.json\", 'w') as warehouse: \n", " json.dump(stuff, warehouse, indent=4)\n", " \n", "def simulation():\n", " \n", " test_data() # initialize the_stuff.json\n", " kirby = Shopper(\"Kirby\", 1000)\n", " \n", " with SuperMarket(\"the_stuff.json\") as market:\n", " try:\n", " market.buy(kirby, \"Snicker-Snacks\", 2)\n", " market.buy(kirby, \"Polly's Peanuts\", 2)\n", " market.buy(kirby, \"Dr. Soap\", 2) # triggers exception\n", " except NoMoney:\n", " print(\"Uh oh, out of money!\")\n", " except OutOfStock:\n", " print(\"Uh oh, out of stock!\")\n", " else:\n", " print(kirby)\n", " print(market.inventory.wares)\n", " print(market)\n", " finally:\n", " pass\n", "\n", "if __name__ == \"__main__\":\n", " simulation()" ] }, { "cell_type": "markdown", "id": "9b04165f", "metadata": {}, "source": [ "For more context, check out [Supermarket Math](https://wikieducator.org/Supermarket_Math) at the Digital Math hub." ] }, { "cell_type": "code", "execution_count": 2, "id": "7bffe5bf", "metadata": {}, "outputs": [], "source": [ "from IPython.display import YouTubeVideo" ] }, { "cell_type": "code", "execution_count": 3, "id": "a8653ef2", "metadata": {}, "outputs": [ { "data": { "image/jpeg": "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\n", "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "YouTubeVideo(\"OMMdm4j3KZM\")" ] }, { "cell_type": "markdown", "id": "2e36ec36", "metadata": {}, "source": [ "Another story goes with our Secretary and BusyOfficeWorker pattern, used to introduce ```__set__``` and ```__get__``` methods.\n", "\n", "The Secretary type need only be assigned to (self.sec = \"message\") or retrieved from (messages = self.sec) where sec appears in multiple selves, as a class-level variable." ] }, { "cell_type": "code", "execution_count": 4, "id": "2c615c13", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Taking messages...\n", "\n", "Worker Shelly Global Data Corporation: Inbox:\n", "Empty\n", "Secretary Frank: thank you. Saving.\n", "Secretary Frank: thank you. Saving.\n", "Secretary Frank: thank you. Saving.\n", "Secretary Frank: thank you. Saving.\n", "Secretary Frank: thank you. Saving.\n", "\n", "Retrieving messages...\n", "\n", "worker1:\n", "Worker Cindy Global Data Corporation: Inbox:\n", "Time: 2022-06-30 13:58:16.603982-07:00\n", " Message: Hello, this is to remind you...\n", "Time: 2022-06-30 13:58:16.604270-07:00\n", " Message: Hello, this is to remind you...\n", "Time: 2022-06-30 13:58:16.604353-07:00\n", " Message: Spam call\n", "\n", "worker2:\n", "Worker Shelly Global Data Corporation: Inbox:\n", "Time: 2022-06-30 16:58:16.604056-04:00\n", " Message: Your dentist appointment for...\n", "Time: 2022-06-30 16:58:16.604138-04:00\n", " Message: Your car is ready for pickup.\n" ] } ], "source": [ "# %load busyoffice.py\n", "#!/usr/bin/env python3\n", "\"\"\"\n", "Created on Tue Dec 5 14:30:35 2017\n", "Modified April 5, 2018\n", "Modified May 24, 2018\n", "Modified June 5, 2021\n", "\n", "@author: Kirby Urner\n", "\n", "alice = Secretary(\"Alice\") # implements Descriptor protocol\n", "\n", "(data descriptor = full service)\n", "\n", "The idea being, self.secretary knows the self that \n", "calls it, the self.worker, and so can save the \n", "message directly in self.worker.__dict__['inbox']\n", "\n", "Think of other secretaries for other tasks besides\n", "taking taking messages.\n", "\"\"\"\n", "\n", "import datetime, pytz\n", "\n", "UTC = pytz.timezone('UTC')\n", "PACIFIC = pytz.timezone('US/Pacific')\n", "EASTERN = pytz.timezone('US/Eastern')\n", "\n", "class Secretary:\n", "\n", " \"\"\"\n", " descr.__get__(self, obj, type=None) --> value\n", "\n", " descr.__set__(self, obj, value) --> None\n", "\n", " descr.__delete__(self, obj) --> None\n", " \n", " 'inbox' is hard-coded as one of the api elements of an obj\n", " \"\"\"\n", " \n", " def __init__(self, nm):\n", " self.name = nm\n", " \n", " def __set__(self, obj, val):\n", " print(f\"Secretary {self.name}: thank you. Saving.\")\n", " if not 'inbox' in obj.__dict__:\n", " obj.inbox = [ ] # initialize empty list\n", " # (datetime, message) appended to list\n", " obj.inbox.append((datetime.datetime.now(tz=UTC), val))\n", "\n", " def __get__(self, obj, cls):\n", " print(f\"Worker {obj.worker} {cls.company}: Inbox:\") # obj is the worker's self, cls its class\n", " if ('inbox' not in obj.__dict__) or ({} == obj.__dict__):\n", " return 'Empty'\n", " else:\n", " return [(message[0].astimezone(tz=obj.timezone), message[1])\n", " for message in obj.inbox]\n", " \n", "class BusyOfficeWorker:\n", "\n", " my_assistant = Secretary(\"Frank\") # add a layer of politeness\n", " company = \"Global Data Corporation\"\n", "\n", " def __init__(self, worker_bee, tz=PACIFIC):\n", " self.worker = worker_bee\n", " self.timezone = tz\n", "\n", " def leave_message(self, message):\n", " self.my_assistant = message # triggers __set__\n", "\n", " def pickup_message(self):\n", " return self.my_assistant # that'll be *my* inbox, triggers __get__\n", "\n", " def empty_inbox(self):\n", " # simplest possible\n", " if \"inbox\" in self.__dict__: # if there\n", " del self.__dict__[\"inbox\"]\n", " \n", " def report(self):\n", " ms = self.pickup_message()\n", " if ms == 'Empty':\n", " print('Empty')\n", " else:\n", " for m in ms:\n", " print(f\"Time: {m[0]}\\n Message: {m[1]}\")\n", "\n", "def simulation():\n", " # incoming pipeline\n", " print(\"Taking messages...\\n\")\n", " worker1 = BusyOfficeWorker(\"Cindy\", PACIFIC)\n", " worker2 = BusyOfficeWorker(\"Shelly\", EASTERN)\n", "\n", " worker2.report() # testing empty inbox situation\n", "\n", " worker1.leave_message(\"Hello, this is to remind you...\")\n", " worker2.leave_message(\"Your dentist appointment for...\")\n", " worker2.leave_message(\"Your car is ready for pickup.\")\n", " worker1.leave_message(\"Hello, this is to remind you...\")\n", " worker1.leave_message(\"Spam call\")\n", "\n", " # retrieval process\n", " print(\"\\nRetrieving messages...\\n\")\n", " print(\"worker1:\") \n", " worker1.report()\n", "\n", " print()\n", " print(\"worker2:\")\n", " worker2.report()\n", " \n", "if __name__ == \"__main__\":\n", " simulation()" ] }, { "cell_type": "code", "execution_count": 5, "id": "eadc8bda", "metadata": {}, "outputs": [ { "data": { "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEABALDBoYFhsaGRoeHRsfICclHyAgICgnKCclLicyMC0tLS01PFBCNThLOS0tRWFFS1NWW1xbMkFlbWVYbFBZW1cBERISGRYZLxsaL1c9Nz9XV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXXVdXXVdXV1dXV1dXV//AABEIAWgB4AMBIgACEQEDEQH/xAAbAAEAAQUBAAAAAAAAAAAAAAAAAQIDBAUGB//EAE0QAAEDAQQECAoGCQMEAgMAAAEAAhEDBBIhMQVBUZEGExYiYXGx0QcUMlJTgYKSocEVI0JUctIXMzREYnOy4fAkNaJDZMLxJWNFk+L/xAAaAQEBAQEBAQEAAAAAAAAAAAAAAQIDBAUG/8QALhEBAAIBBAEDAgQHAQEAAAAAAAECEQMSITEEE0FRMpEUYXHwIiMzUlOB4XLR/9oADAMBAAIRAxEAPwDz9ERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBF2fg/0RZ7Vx/H0hUu3bsk4TOxdeeCejpjxZknGLzst6Dx1F7JyQ0f8Adm73d6gcEdHH92Zvd3oPHEXsD+DGjGm66hTBOouPeqzwR0eP3Zu93eg8cRewcltHTHizJ2Xnd6nkpo/7q3e7vQePIvYeSmjvurd7u9OSmj/urd7u9B48i9h5J6P+6t3u705KaO+6t3u70HjyL2Hkpo/7q3e7vUcltHTHizJiYvOmN6Dx9F7AeCujhnZme87vTkvo7A+LMxy5zsfig8fRewHgzo0GDZqc7LxntVR4LaO+6s3u70HjqL2FvBfRxEiysI2gu71PJXR/3Vm93eg8dRexcldH/dWb3d6cldH/AHVm93eg8dRew8lNH/dW73d6clNH/dW73d6Dx5F7DyU0f91bvd3pyU0f91bvd3oPHkXsPJTR/wB1bvd3pyU0f91bvd3oPHkXsPJTR/3Vu93enJTR/wB1bvd3oPHkXsPJTR/3Vu93enJTR/3Vu93eg8eR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\n", "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "YouTubeVideo(\"g0RTmVSCsiI\")" ] } ], "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.12" } }, "nbformat": 4, "nbformat_minor": 5 }