#!/usr/bin/env python # coding: utf-8 # ##### Python for High School (Summer 2022) # # * [Table of Contents](PY4HS.ipynb) # * Open in Colab # * [![nbviewer](https://raw.githubusercontent.com/jupyter/design/master/logos/Badges/nbviewer_badge.svg)](https://nbviewer.org/github/4dsolutions/elite_school/blob/master/Py4HS_MakingTypes.ipynb) # # # In[1]: import datetime print(datetime.datetime.now().isoformat()) # # Python for High School: Making Types # # The idea of "types" is less a matter of computer science or mathematics, and more a matter of ordinary language, wherein we learn to group things by their type. # # The screwdriver is a type of tool. A car is a type of motorvehicle. A motorvehicle is a type of vehicle. # # If I say "Sheila is a type of animal" then the game might be to guess which one. # # Is Sheila a dog, cat, octopus or parrot? # # Depending on which "type" (or "species") of animal Sheila is, we might expect certain capabilities, behaviors, attributes. If Sheila is a giraffe, we would expect she has a long neck. # # Humans have presumably learned to typify objects ever since they started thinking, whenever that was (a matter of ongoing research). Identifying the type of something, say a plant, is a core survival skill. # # Which plants are safe to eat? We know by recognizing their type. # [Meetup 2: Markdown, Code Cells and Animal Types](Py4HS_July_8_ipynb) # # Screen Shot 2022-07-10 at 6.06.36 PM # ### Type-Oriented Computer Languages # # "Oriented" means "pointing in that direction" or "especially fit for that use". Python is both type oriented and object oriented. We think in terms of objects, each of a specific type or types (an object may be of more than one type in Python). # # In Python and other similar computer languages (such as Java), we're free to set up a kind of "type scheme" like an "ecosystem" of several kinds of object. Perhaps a "jungle" would be a good word, in terms of variety and propensity to grow. # # This jungle is designed to get work done in some way. One object gets data from a gigantic database. Another object wraps that data in pretty-to-look-at styling (HTML + CSS). These objects pass objects between them. # # The surrounding Notebook is perhaps our guide book, explaining what's here and what it does. # # Let's code up a Parrot type: # In[2]: class Parrot: """ A type -- same idea as class (the class of all Parrots) """ def __init__(self, nm): # triggered by calling Parrot self.name = nm def __call__(self, say_it): # triggered by calling a Parrot self return f"{self.name} says '{say_it}'" def __repr__(self): # represents a Parrot self, called "the repper" return f"Parrot named {self.name}" # You may remember from meetup one, our introduction to "special names" (also known as "magic methods"). They're the ones with the double-underline on both sides. Python uses the underline character extensively. That's one of its hallmark traits. # # ```__repr__```, for example, the last method under Parrot (it didn't have to be last -- method order is arbitrary meaning it doesn't matter), is a special name, and what we call "the repper". # # When an object needs to represent itself as a string, a set of characters, this method will be used. Unless, that is, there's also an ```__str__``` in which case it gets priority. We'll try that later. # In[3]: pet = Parrot("Sheila") # create an instance of the Parrot type, triggers __init__ # In[4]: pet # __repr__ fires (executes) # In[5]: type(pet) # what type of object am I? repper of the class itself # In[6]: isinstance(pet, Parrot) # am I an instance of a Parrot? # In[7]: pet("Hello! Hello!") # triggers __call__ # Weird Sarah Again # # Now lets make a Dog type (same as class) that does a little more: # In[8]: class Dog: """ A type """ def __init__(self, nm): self.name = nm self.stomach = [ ] def eat(self, food): self(food) # triggers __call__ def __call__(self, food): self.stomach.append(food) def __repr__(self): return f"Dog named {self.name}" # In[9]: dog_1 = Dog("Rover") # In[10]: dog_1 # In[11]: dog_1.eat('🍕') # could be the word 'pizza' also # In[12]: dog_1.stomach # In[13]: dog_1('🍩') # same as .eat, but this time a donut # In[14]: dog_1.stomach # In[15]: from random import choice # warning: this is not healthy food for a real dog. For learning purposes only. foods = set(['🍩', '🍌', '🍪', '🍕']).union(['🍩','🍨', '🍰','🍪']) foods = list(foods) # choice uses left-to-right indexing so convert to a list kennel = [Dog("Rover"), Dog("Sheila"), Dog("Fido"), Dog("Wolfie")] for dog in kennel: for _ in range(3): dog.eat(choice(foods)) kennel # In[16]: for dog in kennel: print(f"{dog.name:<10}: {dog.stomach}") # Crows # # Back to our Parrot, but modified, to become a Bird: # In[17]: class Bird: """ Adapted from Parrot, adding __str__ """ def __init__(self, nm): self.name = nm def __call__(self, say_it): return f"{self.name} says '{say_it}'" def __repr__(self): return f"{self.__class__.__name__} named {self.name}" def __str__(self): # testing what this does return f"I am {self.name}" # In[18]: crow = Bird("123") # In[19]: print(crow) # triggers __str__ # In[20]: crow # triggers __repr__ # Lets remember how Parrot works. We still have `pet` from earlier, an instance of the Parrot type. # In[21]: pet # The rule is, print(obj) which fires str(obj), will look for a ```__str__``` method first, allowing it to be something different from ```__repr__```. Oft times when designing types, a programmer will appreciate having this optional distinction, between ```__str__``` and ```__repr__```. # In[22]: print(pet) # to __str__ so fall back to __repr__ # In[23]: str(pet) # In[24]: repr(pet) # In[25]: repr(crow) # fire the repper # ## More About Special Names # # *And now for something completely different* # # [Ornery Type](https://replit.com/@kurner/Ornery-Type#main.py) at Replit.it # # If the link is operational, it will take you to a codepen at Replit where a class named Ornery is defined. # # "Ornery" means "in a bad mood" which is the theme for this type's personality. # # The special names are more of the usual ones that Python offers, not names we make up. # # How does an object work with brackets? That's for ```__setitem__``` and ```__getitem__``` to determine. # # How should an object behave when we access it with a dot (the "accessor"?). That's a job for ```__getattr__``` (pronounced "get atter"). We want an object to check for the attribute and only run ```__getattr__``` as a last resort, if the attribute is not located. # In[26]: class Anything: def __getattr__(self, attr): print(f"Do something with {attr}") # In[27]: any_object = Anything() # give it some attributes any_object.color = "brown" any_object.edible = True any_object.name = "cookie" # In[28]: any_object.__dict__ # here's where internal attributes get saved # In[29]: any_object.color # In[30]: any_object.age # this doesn't exist, so run __getattr__ # Remember that we do not invent our own special names unless we are extending the Python language itself. That's not what we usually want to do. The special names we're given are well thought out, and Python knows exactly what to do with each one of them. # # The code is actually here in this repo as well, so why not just load it? # In[31]: # %load ornery.py """ This type of object gets along with nobody! 自 = self in Chinese, disregard errors """ class Ornery: def __init__(自, name="Fred"): 自.name = name print("A sourpuss is born!") def __getitem__(自, key): return "How dare you touch me with those brackets!" def __call__(自, *args, **kwargs): return "Don't call me at home!" def __getattr__(自, attr): return "I'm insulted you'd suppose I'd have {}".format(attr) def __repr__(自): return "Don't bother me! Go away." def __invert__(自): return "I can't invert, are you kidding?" # ## Operator Overloading # # How does any of this stuff relate to mathematics? # # We have seen the beginnings of data structures and algorithms (methods), working together in the form of types, with objects of those types. # # In Python, some of the special names govern the behavior of mathematical operators, such as the add, subtract, multiply and divide symbols: +, -, \*, /. As a programmer, you are allowed to specify what these will do when used with your own types. # In[32]: class Concatter: def __init__(self, chrs): self.value = chrs def __add__(self, other): return Concatter(self.value + other.value) def __str__(self): return self.value def __repr__(self): return f"Concatter('{self.value}')" # In[33]: c1 = Concatter("The cat") c2 = Concatter(" chased") c3 = Concatter(" the mouse!") # In[34]: c1 # In[35]: c1 + c2 # In[36]: c1 + c2 + c3 # In[37]: print(c1 + c2 + c3) # The string type all by itself is already perfectly able to concatenate with other strings, by means of the plus sign operator. # # The Concatter type is therefore not really needed for anything, except to show how ```__add__``` works. # # The ```__add__``` method is fired whenever a Concatter object is followed with a plus symbol (+). # In[38]: c1.__add__(c3) # In[39]: c1 + c3 # In[40]: 'abc'.__add__('def') # Python is using these same methods in its own types # ## Polyhedrons # # What math objects might we usefully define as a type of their own? # # Polyhedrons are a good place to start, in part because they're objects in the conventional sense. They have edges, faces, corners. They have volume and surface area. # # Waterman Polyhedron # # The power rule for any shape is: if you scale all the edges by S, then surface area will resize by a factor of S times S (S to the 2nd power), and volume by a factor of S times S times S (S to the 3rd power). # # Let's code that: # In[45]: class Polyhedron: def __init__(self, name, cl, s, v): self.name = name self.control_length = cl self.surface = s self.volume = v def __mul__(self, factor): """returns a new Polyhedron of changed dimensions, same shape""" return Polyhedron(self.name, self.control_length * factor, self.surface * factor ** 2, self.volume * factor ** 3) def __repr__(self): return f"Polyhedron(name='{self.name}', cl={self.control_length}, s={self.surface}, v={self.volume})" # In[46]: tetrahedron = Polyhedron("Tetrahedron", 1, 4, 1) # In[47]: tetrahedron * 2 # triggers __mul__ # In[48]: tetrahedron * 3 # #### Resources # # * [More Advanced Polyhedron Studies](https://github.com/4dsolutions/elite_school/blob/master/TheCabal.ipynb) # # * [Building a Volumes Table](https://github.com/4dsolutions/elite_school/blob/master/TheCabal.ipynb)