{ "cells": [ { "cell_type": "markdown", "id": "cf23f227", "metadata": {}, "source": [ "[Home](Home.ipynb)\n", "\n", "# Object Oriented Programming (OOP)\n", "\n", "Jump to:\n", "\n", "* [Decorators](#Decorators-and-Dataclasses)\n", "* [Properties](#Properties)\n", "* [Iterators](#The-Iterator-Pattern)\n", "* [Descriptors](#The-Descriptor-Pattern)\n", "\n", "\"spyder_day3_1\"" ] }, { "cell_type": "markdown", "id": "c446e7f4", "metadata": {}, "source": [ "The contemporary idea of a \"computer language\" did not arise spontaneously, with the invention of logic chips. \n", "\n", "What would a \"high level language\" even look like? Could we just talk to a computer like we would to a human being? When it comes to recognizing our spoken words and typing them back to us, computers have improved greatly, thanks to machine learning. \n", "\n", "A core project of computer science over the last several decades has been the quest for a common ground for logic circuits and our own, more human way of reasoning.\n", "\n", "Language designers realized we typically think in terms of objects (nouns) that have attributes (adjectives) and behaviors (verbs). Furthermore, we have the idea that some types of objects have a family resemblance to other objects. \n", "\n", "Might we avoid reinventing the wheel all the time, and reuse code, even when defining new kinds of object? That was (and still is) the purpose of the Object Oriented Paradigm (OOP).\n", "\n", "Smalltalk, by Alan Kay and friends, implemented OOP with breakthrough clarity and consistency and many programmers experienced a huge boost in productivity. New languages based on OOP followed, such as C++, Java, C# and Python." ] }, { "cell_type": "markdown", "id": "e66630c4", "metadata": {}, "source": [ "Lets look at a rather simple piece of Python code, defining what we call a class:" ] }, { "cell_type": "code", "execution_count": 1, "id": "a76267c7", "metadata": {}, "outputs": [], "source": [ "class Snake:\n", "\n", " def __init__(self, name):\n", " self.name = name\n", " self.stomach = [ ]\n", "\n", " def eat(self, food):\n", " self.stomach.append(food)\n", "\n", " # any_snake(\"🐹\") synonymous with any_snake.eat(\"🐹\")\n", " def __call__(self, food):\n", " self.eat(food)\n", "\n", " def __repr__(self):\n", " return f\"Snake named {self.name} at {id(self)}\"" ] }, { "cell_type": "markdown", "id": "7894e3cd", "metadata": {}, "source": [ "The indendation is syntactically necessary. You must align your blocks vertically, to designate scope. Many languages use curly braces for this purpose. Python looks less cluttered thanks to their absence.\n", "\n", "Now lets use the above class, first by instancing it (making an instance, an individual), and then by feeding it using its own methods:" ] }, { "cell_type": "code", "execution_count": 2, "id": "3aca6afa", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['🐹', 'snack']" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "any_snake = Snake(\"Naga\") # triggers __init__\n", "any_snake(\"🐹\") # triggers __call__\n", "any_snake.eat(\"snack\")\n", "any_snake.stomach # accessing an attribute" ] }, { "cell_type": "code", "execution_count": 3, "id": "67d0b35e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Snake named Naga at 4432499024" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "any_snake # triggers __repr__" ] }, { "cell_type": "code", "execution_count": 4, "id": "26b2dc8d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Snake named Twila at 4443385168" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "another_snake = Snake(\"Twila\")\n", "another_snake" ] }, { "cell_type": "markdown", "id": "eca0cdc1", "metadata": {}, "source": [ "The classes below show off more of the special names (```__ribs__```). These allow you, the programmer, to take control of arithemtic operators, boolean operators, and more. \n", "\n", "You need not invent a behavior for all of these optional features. You may also subclass an already existing type, including a built-in type, and just add a few new behaviors of your own." ] }, { "cell_type": "code", "execution_count": 5, "id": "c4ea3680", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['🍋', '🍟', '🍑', '🍏', '🍌', '🎂', '🍋', '🍩', '🍈', '🍒']\n" ] } ], "source": [ "#!/usr/bin/env python3\n", "# -*- coding: utf-8 -*-\n", "\"\"\"\n", "Created on Sat Dec 26 13:38:58 2020\n", "\n", "@author: Kirby\n", "\"\"\"\n", "\n", "from random import choice\n", "from fooding import foods, fruits\n", "\n", "foods = foods + fruits\n", "\n", "class Animal():\n", " \n", " def __init__(self, name, breed):\n", " self.name = name\n", " self.breed = breed\n", " self.stomach = []\n", " \n", " def __eq__(self, other):\n", " if (self.name == other.name \n", " and self.breed == other.breed):\n", " return True\n", " return False\n", " \n", " def __mul__(self, other):\n", " pass\n", " \n", " def __add__(self, other):\n", " return Animal(self.name + other.name, breed = \"mutt\")\n", " \n", " def __call__(self, food):\n", " self.eat(food)\n", " \n", " def eat(self, food):\n", " self.stomach.append(food)\n", " \n", " def __getitem__(self, arg):\n", " return self.stomach[arg]\n", " \n", "class Dog(Animal):\n", " \n", " \n", " def version():\n", " return \"Dog 2.0\"\n", " # old syntax we no longer need\n", " version = staticmethod(version)\n", " \n", " tricks = [\"play dead\", \"beg\"]\n", " \n", " @classmethod\n", " def add_trick(cls, newtrick):\n", " cls.tricks.append(newtrick)\n", "\n", " def bark(self, n = 1):\n", " return \"Bark! \" * n\n", "\n", " def do_trick(self):\n", " return choice(self.tricks)\n", " \n", " def __repr__(self):\n", " return \"Dog('{}', '{}') at {}\".format(self.name, self.breed, id(self))\n", "\n", "class Cat(Animal):\n", " pass\n", "\n", "\n", "dog1 = Dog(\"Rover\", \"Dog\")\n", "for _ in range(10):\n", " dog1.eat(choice(foods))\n", " \n", "print(dog1.stomach)" ] }, { "cell_type": "markdown", "id": "93a2af28", "metadata": {}, "source": [ "### OOP and Atoms\n", "\n", "\"But what exactly is an 'object'?\" you might ask? From one point of view, it's a committed piece of memory, a place to use energy in predifined ways to preserve information. \n", "\n", "Think of an atom as a kind of object. Think how it takes energy to read from or write to atoms, thereby changing their state in some way. We can shine light on atoms to learn about their light spectra, and thereby classify them into elements. We may also weigh them, in a mass spectrometer." ] }, { "cell_type": "code", "execution_count": 6, "id": "b23c96d5", "metadata": {}, "outputs": [], "source": [ "import requests # send HTTP requests over the internet\n", "\n", "lookup_database = \"http://thekirbster.pythonanywhere.com/api/elements\"\n", "\n", "class Atom:\n", " def __init__(self, element):\n", " self.element = element\n", " # lookup other attributes in a data table\n", " self.info = requests.get(lookup_database, \n", " params={'elem': element})" ] }, { "cell_type": "code", "execution_count": 8, "id": "deec0b31", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[79, 'Au', 'Gold', 196.9665695, 'transition metal', 1493462392, 'KTU']" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "gold = Atom(\"Au\")\n", "gold.info.json()" ] }, { "cell_type": "markdown", "id": "9c27bfc4", "metadata": {}, "source": [ "### OOP and Polyhedrons\n", "\n", "Think of a polyhedron as another kind of object. By calling something a polyhedron, even if it is also a marble sculpture, or piece of foam rubber, we draw attention to its purely geometric attributes, such as its overall shape. How many edges does it have? How many corners? What is its volume, relative to some unit measure?\n", "\n", "Both atoms and polyhedrons are \"things\" (nouns) and have \"attributes\". These sound like computer science terms, and they are, but their meaning starts with ordinary language and should be anchored there. Attributes are somewhat like adjectives and give the properties or measures of a thing.\n", "\n", "What we call a class is like a category, also called a type. Again, these words get used in precise ways within mathematics, but the intuitions are rooted in ordinary language. We know a breed of dog is also a class of dog, such as poodle. But then we have specific poodles, with names and ID tags, individual pets.\n", "\n", "We know that in the world of polyhedrons (or polyhedra -- either is OK), we have the tetrahedron. \n", "\n", "The regular tetrahedron has all edges the same length, and the only way for that to work is to have all the angles be the same as well, namely sixty degrees. \n", "\n", "We might have irregular tetrahedrons just as easily, and some of them might get names. Perhaps we'd like to dissect or disassemble a regular tetrahedron into irregular slivers, all congruent to each other. We could call these \"A Modules\".\n", "\n", "\"A\n", "\n", "\"24" ] }, { "cell_type": "code", "execution_count": 26, "id": "4103e46a", "metadata": {}, "outputs": [], "source": [ "lookup_database = \"http://thekirbster.pythonanywhere.com/api/shapes\"\n", "\n", "class Polyhedron(object):\n", " \"\"\"\n", " Methods to: \n", " rotate around an axis (spin)\n", " slide along a vector (translate)\n", " resize (scale)\n", " \"\"\"\n", " \n", "class Tetrahedron(Polyhedron):\n", " \"\"\"\n", " Preset topology of points a,b,c,d \n", " connected into four faces by six edges\n", " \"\"\"\n", " \n", "class RegTet(Tetrahedron):\n", " \"\"\"\n", " All edges same length. Could add attributes.\n", " \"\"\"\n", " def __init__(self, shape=\"tetra\"):\n", " self.shape = shape\n", " # lookup other attributes in a data table\n", " self.info = requests.get(lookup_database, \n", " params={'shape': shape})\n", " \n", "class Amod(Tetrahedron):\n", " \"\"\"\n", " Tetrahedron with specific edge lengths in a\n", " specific location. Could add attributes \n", " such as color, charm, age, weight...\n", " \"\"\"\n", " \n", " def __init__(self, location=(0,0,0,0)):\n", " self.locus = location" ] }, { "cell_type": "code", "execution_count": 25, "id": "edfbb955", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[1, 'tetrahedron', 'tetra', 4, 4, 6, 1, 1.0, 1471705058, 'KTU']" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "regtet = RegTet()\n", "regtet.info.json()" ] }, { "cell_type": "markdown", "id": "dd06f67e", "metadata": {}, "source": [ "How realistic is it to have the process of initializing an object require a request over the internet, for information about attributes?\n", "\n", "Actually it's pretty standard to populate an object's attributes by reading from a database. Exactly when and how that's done is often left to what's called an ORM or Object Relational Mapper." ] }, { "cell_type": "markdown", "id": "6770b8fe", "metadata": {}, "source": [ "### Decorators and Dataclasses\n", "\n", "The idea of an object is akin to that of a cargo ship, with its own crew, cranes, power supply. A ship with the tools to work with its own cargo is like an object containing data, but also ways of working with that data.\n", "\n", "Have you encountered [the section on NamedTuples](Bridge2OOP.ipynb) yet? That's where you get to name the \"fields\" of a tuple type, such as a chemical element. You might want Symbol (one or two letters), Name (Hydrogen), Atomic Number (integer), Atomic Mass (floating point).\n", "\n", "Dataclasses let us do something similar." ] }, { "cell_type": "code", "execution_count": 6, "id": "68ea0599", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Element(Symbol='H', Name='Hydrogen', Atomic_number=1, Atomic_mass=1.008)\n", "{'Symbol': 'H', 'Name': 'Hydrogen', 'Atomic_number': 1, 'Atomic_mass': 1.008, 'Type': 'diatomic metal'}\n", "Element(Symbol='He', Name='Helium', Atomic_number=2.0, Atomic_mass=4.0026)\n" ] } ], "source": [ "from dataclasses import dataclass, field\n", "import dataclasses as d\n", "\n", "@dataclass\n", "class Element:\n", " Symbol: str\n", " Name: str\n", " Atomic_number: int\n", " Atomic_mass: float\n", " Type: str = field(repr=False, compare=False)\n", "\n", "hydrogen = Element(\"H\", \"Hydrogen\", 1, 1.008, \"diatomic metal\")\n", "print(hydrogen)\n", "print(d.asdict(hydrogen))\n", "\n", "helium = Element(\"He\", \"Helium\", 2.0, 4.0026, \"gas\")\n", "print(helium)" ] }, { "cell_type": "markdown", "id": "c9177663", "metadata": {}, "source": [ "The ```@dataclass``` thing is called a decorator. Using decorator syntax, a callable (dataclass) swallows the thing that's under it, being defined, either a class or a function (something with def), and spits out something else, but with the same name. \n", "\n", "This \"something else\" is a modified or enhanced version of what it swallows. You might want to [think of \"abuction\"](https://github.com/4dsolutions/Python5/blob/master/Abducted!.ipynb), as when an innocent bystander is sucked up into a flying saucer (in science fiction) and then returned to Earth, somehow the same, yet different." ] }, { "cell_type": "code", "execution_count": 7, "id": "0b39e65a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "No tattoo found\n", "👽\n" ] } ], "source": [ "def flying_saucer(bystander):\n", " bystander.tattoo = \"👽\"\n", " return bystander\n", "\n", "def innocent(): # any function\n", " pass\n", "\n", "try:\n", " print(innocent.tattoo)\n", "except:\n", " print(\"No tattoo found\")\n", " \n", "@flying_saucer\n", "def innocent():\n", " pass\n", "\n", "try:\n", " print(innocent.tattoo)\n", "except:\n", " print(\"No tattoo found\")" ] }, { "cell_type": "markdown", "id": "7506ff26", "metadata": {}, "source": [ "### Properties\n", "\n", "Now that we've learned about decorator syntax, lets check out one of the most commonly used features that employs this syntax: properties.\n", "\n", "Properties behave like attributes in that we can set and get their values using the assignment operator and/or dot notation. For example, the Circle type below allows us to supply a circle instance radius, area or circumference value by direct assignment.\n", "\n", "However, rather than simply store these values in ```__dict__```, wheels are made to turn. \n", "\n", "Changing the radius means the area and circumference need to change, to keep the circle consistent. \n", "\n", "Likewise changing the area necessitates recomputing the other two. \n", "\n", "Rather than having to explicitly call methods, we let the property decorator show what to run upon setting or getting. " ] }, { "cell_type": "code", "execution_count": 8, "id": "977c55c8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "the_circle: Circle(radius = 5)\n", "Area: 78.53981633974483\n", "Radius when Area=50: 3.989422804014327\n", "Radius with circumference is 2*pi: 1.0\n" ] } ], "source": [ "# %load magic_circle_v2.py\n", "#!/usr/bin/env python3\n", "\"\"\"\n", "Created on Wed Oct 18 15:25:39 2017\n", "\n", "@author: kurner\n", "\"\"\"\n", "\n", "# -*- coding: utf-8 -*-\n", "\"\"\"\n", "Created on Thu Oct 20 15:43:14 2016\n", "Modified Oct 17, 2017\n", "\n", "@author: Kirby Urner\n", "\n", "Made this _v2 with circumference added as a new property\n", "\n", "toggle the import model_property on and off to see\n", "the example works the same either way. model_property\n", "contains a pure Python emulator of the built in\n", "property type.\n", "\n", "Related reading:\n", "https://mail.python.org/pipermail/edu-sig/2016-October/011548.html\n", "\"\"\"\n", "\n", "# from model_property import Property as property\n", "import math\n", "\n", "class Circle:\n", " \"\"\"setting either the radius or area attribute sets the other\n", " as a dependent value. Initialized with radius only, unit\n", " circle by default.\n", " \"\"\"\n", "\n", " def __init__(self, radius = 1):\n", " self.radius = radius\n", "\n", " @property\n", " def area(self):\n", " return self._area\n", " \n", " @property\n", " def radius(self):\n", " return self._radius\n", "\n", " @property\n", " def circumference(self):\n", " return self._circum\n", " \n", " @circumference.setter\n", " def circumference(self, value):\n", " self.radius = value / (2 * math.pi)\n", " \n", " @area.setter\n", " def area(self, value):\n", " self._area = value\n", " self._radius = math.sqrt(self._area / math.pi)\n", " self._circum = 2 * math.pi * self._radius\n", "\n", " @radius.setter\n", " def radius(self, value):\n", " self._radius = value\n", " self._area = math.pi * (self._radius ** 2)\n", " self._circum = 2 * math.pi * self._radius\n", " \n", " def __repr__(self):\n", " return \"Circle(radius = {})\".format(self.radius)\n", "\n", "\n", "if __name__ ==\"__main__\":\n", " # I AM the captain!\n", " the_circle = Circle(5)\n", " print(\"the_circle:\", the_circle)\n", " print(\"Area: \", the_circle.area)\n", " the_circle.area = 50\n", " print(\"Radius when Area=50:\", the_circle.radius)\n", " the_circle.circumference = math.pi * 2\n", " print(\"Radius with circumference is 2*pi: {}\".format(the_circle.radius))" ] }, { "cell_type": "code", "execution_count": 9, "id": "e16c9065", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.5915494309189535" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "the_circle.circumference = 10\n", "the_circle.radius" ] }, { "cell_type": "markdown", "id": "0a6252a3", "metadata": {}, "source": [ "### The Iterator Pattern\n", "\n", "An iterator in Python is an object with a ```__next__``` method. Such objects are the target of for-loop syntax, and *for* may be said to \"hit the next button until exhaustion\" on its target.\n", "\n", "As iterator also needs an ```__iter__``` method as the for loop starts by feeding the target through iter( ) -- we don't see this -- in order to make sure that's what it's dealing with.\n", "\n", "Exhaustian is signalled with a StopIteration exception, which causes a for loop to end normally." ] }, { "cell_type": "code", "execution_count": 10, "id": "4f8fc067", "metadata": {}, "outputs": [], "source": [ "from random import randint\n", "\n", "class It:\n", " \n", " def __next__(self):\n", " val = randint(0,10)\n", " if val == 10:\n", " raise StopIteration\n", " return val\n", " \n", " def __iter__(self):\n", " return self" ] }, { "cell_type": "markdown", "id": "7c808458", "metadata": {}, "source": [ "Run this a few times. The output changes because of how the for loop prints pseudo-random numbers between 0 and 9, until it draws a 10, which causes it to quit gracefully." ] }, { "cell_type": "code", "execution_count": 11, "id": "e45cc6a5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 0 6 3 1 4 Done!\n" ] } ], "source": [ "it = It()\n", "\n", "for x in it:\n", " print(x, end=\" \")\n", " if x == 8:\n", " print(\"Hit an 8!\")\n", " break\n", "else:\n", " print(\"Done!\")" ] }, { "cell_type": "markdown", "id": "89653580", "metadata": {}, "source": [ "Python knows how to turn anything with a ```__getitem__``` method into an iterator. Just feed it n = 0, 1, 2, 3..." ] }, { "cell_type": "code", "execution_count": 12, "id": "a8b82bd1", "metadata": {}, "outputs": [], "source": [ "class Iterable:\n", " \n", " def __getitem__(self, n):\n", " val = randint(0,10)\n", " if val == 10:\n", " raise StopIteration\n", " return val" ] }, { "cell_type": "code", "execution_count": 13, "id": "908d45c6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6 9 0 8 6 2 8 7 1 0 2 8 " ] } ], "source": [ "for x in Iterable():\n", " print(x, end=\" \")" ] }, { "cell_type": "markdown", "id": "5004f3aa", "metadata": {}, "source": [ "Iterators do not have to invoke StopIterator internally. One is allowed to make an open ended (undelimited) iterator the target of a for-loop (example: give the next prime number). In that case, the for-loop needs other ways of stopping." ] }, { "cell_type": "markdown", "id": "b5f34984", "metadata": {}, "source": [ "### The Descriptor Pattern\n", "\n", "Python's dunder methods and names provide a kind of \"self awareness\" that enables types of object to \"know\" when they're being used in certain ways e.g. assigned to, called, or selected from, by means of the operators ```=```, ```()```, or ```[ ]``` respectively.\n", "\n", "We've seen that ```__call__``` along with ```__getitem__``` and ```__setitem__``` are the dunder methods associated with calling and selecting, but what dunder methods get triggered by the act of setting or getting the value of something? We've looked at how @property has a role, but then how does ```property``` (a built-in) work [internally](https://docs.python.org/3/howto/descriptor.html#properties)?" ] }, { "cell_type": "markdown", "id": "bfc69d83", "metadata": {}, "source": [ "Let's think about the job of receptionist, or secretary. Many of our most responsible jobs come with the title of Secretary, which has the word \"secret\" in it. A receptionist has a sense of what's appropriate and relevant when dealing with a public. She or he may be a superb diplomat. \n", "\n", "When a group of practitioners get together to set up a practice, they will often agree amongst themselves to hire a public-facing diplomat to screen and field calls, set up appointments, communicate messages.\n", "\n", "Imagine a type that acts like a secretary in that it's also aware of the client object, the practitioner, for whom a message is being saved. As a secretary, I take calls for a clique of practicing geeks, and store them appropriately in the inbox for whom any given message was intended. I may also be conversational with the caller, providing them with the pleasant experience of not having to contend with AI.\n", "\n", "In the code below, each BusyWorker instance shares a class-level Secretary. When a call comes in, it's to a specific BusyWorker, and the Secretary knows which one. The message gets saved to the worker's inbox. When the worker is ready to consult his or her inbox, Secretary has the means to fish them up.\n", "\n", "Keeping this story in mind, while eyeballing the code, will help you appreciate the role of dunder methods ```__set__``` and ```__get__```." ] }, { "cell_type": "code", "execution_count": 19, "id": "3cbd28a8", "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: 2021-06-13 12:31:05.860486-07:00\n", " Message: Hello, this is to remind you...\n", "Time: 2021-06-13 12:31:05.861313-07:00\n", " Message: Hello, this is to remind you...\n", "Time: 2021-06-13 12:31:05.861546-07:00\n", " Message: Spam call\n", "\n", "worker2:\n", "Worker Shelly Global Data Corporation: Inbox:\n", "Time: 2021-06-13 15:31:05.860822-04:00\n", " Message: Your dentist appointment for...\n", "Time: 2021-06-13 15:31:05.861067-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": 17, "id": "8bf236af", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[0;31mSignature:\u001b[0m \u001b[0mpytz\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimezone\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mzone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m\n", "Return a datetime.tzinfo implementation for the given timezone\n", "\n", ">>> from datetime import datetime, timedelta\n", ">>> utc = timezone('UTC')\n", ">>> eastern = timezone('US/Eastern')\n", ">>> eastern.zone\n", "'US/Eastern'\n", ">>> timezone(unicode('US/Eastern')) is eastern\n", "True\n", ">>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)\n", ">>> loc_dt = utc_dt.astimezone(eastern)\n", ">>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'\n", ">>> loc_dt.strftime(fmt)\n", "'2002-10-27 01:00:00 EST (-0500)'\n", ">>> (loc_dt - timedelta(minutes=10)).strftime(fmt)\n", "'2002-10-27 00:50:00 EST (-0500)'\n", ">>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt)\n", "'2002-10-27 01:50:00 EDT (-0400)'\n", ">>> (loc_dt + timedelta(minutes=10)).strftime(fmt)\n", "'2002-10-27 01:10:00 EST (-0500)'\n", "\n", "Raises UnknownTimeZoneError if passed an unknown zone.\n", "\n", ">>> try:\n", "... timezone('Asia/Shangri-La')\n", "... except UnknownTimeZoneError:\n", "... print('Unknown')\n", "Unknown\n", "\n", ">>> try:\n", "... timezone(unicode('\\N{TRADE MARK SIGN}'))\n", "... except UnknownTimeZoneError:\n", "... print('Unknown')\n", "Unknown\n", "\u001b[0;31mFile:\u001b[0m ~/opt/anaconda3/lib/python3.7/site-packages/pytz/__init__.py\n", "\u001b[0;31mType:\u001b[0m function\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "? pytz.timezone" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" } }, "nbformat": 4, "nbformat_minor": 5 }