Last week we looked at inheritance, building a general class that we could then extend with additional functionality for special situations.
Each of the classes we create inheriting from our general class can be thought of as having a 'is-a' relationship with the general class. For example, looking at our Item example from last week Equipment is a Item, Consumable is a Item.
In [ ]:
class Item(object):
def __init__(self, name, description, location):
self.name = name
self.description = description
self.location = location
def update_location(self, new_location):
pass
class Equipment(Item):
pass
class Consumable(Item):
def __init__(self, name, description, location, initial_quantity, current_quantity, storage_temp, flammability):
self.name = name
self.description = description
self.location = location
self.initial_quantity = initial_quantity
self.current_quantity = current_quantity
self.flammability = flammability
def update_quantity_remaining(self, amount):
pass
In week 3 we took example projects and broke them down into a collection of different classes. Many of you chose the cookbook example for the assignment and questioned whether things like ingredients should be attributes on the recipe class or classes in their own right. Often the answer is both. These are the interactions that change a collection of different classes into a functioning program. This is called composition. The Recipe object is a composite object, it has ingredients, it has instructions, etc.
This week we will look at how we can design our classes to be easy to use, for both programmer-class and class-class interactions.
In [11]:
class Ingredient(object):
"""The ingredient object that contains nutritional information"""
def __init__(self, name, carbs, protein, fat):
self.name = name
self.carbs = carbs
self.protein = protein
self.fat = fat
def get_nutrition(self):
"""Returns the nutritional information for the ingredient"""
return (self.carbs, self.protein, self.fat)
class Recipe(object):
"""The Recipe object containing the ingredients"""
def __init__(self, name, ingredients):
self.name = name
self.ingredients = ingredients
def get_nutrition(self):
"""Returns the nutritional information for the recipe"""
nutrition = [0, 0, 0]
for amount, ingredient in self.ingredients:
nutrition[0] += amount * ingredient.carbs
nutrition[1] += amount * ingredient.protein
nutrition[2] += amount * ingredient.fat
return nutrition
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)),
(30, Ingredient('Oil', 0, 0, 1)),
(36, Ingredient('Sugar', 1, 0, 0)),
(7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
(560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
In [27]:
import requests
In [29]:
r = requests.get('https://api.github.com/repos/streety/biof509/events')
print(r.status_code)
print(r.headers['content-type'])
In [42]:
print(r.text[:1000])
In [41]:
print(r.json()[0]['payload']['commits'][0]['message'])
In [43]:
type(r)
Out[43]:
In [15]:
import pandas as pd
In [18]:
data = pd.DataFrame([[0,1,2,3], [4,5,6,7], [8,9,10,11]], index=['a', 'b', 'c'], columns=['col1', 'col2', 'col3', 'col4'])
data
Out[18]:
In [26]:
print(data.shape)
In [22]:
print(data['col1'])
print(data.col1)
In [21]:
import matplotlib.pyplot as plt
%matplotlib inline
data.plot()
Out[21]:
In [25]:
data.to_csv('Wk05-temp.csv')
data2 = pd.read_csv('Wk05-temp.csv', index_col=0)
print(data2)
In [ ]:
In [ ]:
In [13]:
class Ingredient(object):
"""The ingredient object that contains nutritional information"""
def __init__(self, name, carbs, protein, fat):
self.name = name
self.carbs = carbs
self.protein = protein
self.fat = fat
def __repr__(self):
return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name, self.carbs, self.protein, self.fat)
def get_nutrition(self):
"""Returns the nutritional information for the ingredient"""
return (self.carbs, self.protein, self.fat)
class Recipe(object):
"""The Recipe object containing the ingredients"""
def __init__(self, name, ingredients):
self.name = name
self.ingredients = ingredients
def get_nutrition(self):
"""Returns the nutritional information for the recipe"""
nutrition = [0, 0, 0]
for amount, ingredient in self.ingredients:
nutrition[0] += amount * ingredient.carbs
nutrition[1] += amount * ingredient.protein
nutrition[2] += amount * ingredient.fat
return nutrition
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)),
(30, Ingredient('Oil', 0, 0, 1)),
(36, Ingredient('Sugar', 1, 0, 0)),
(7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
(560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
Viewing the ingredients now looks much better. Let's now look at the get_nutrition method.
There are still a number of areas that could be improved
In [1]:
class Ingredient(object):
"""The ingredient object that contains nutritional information"""
def __init__(self, name, carbs, protein, fat):
self.name = name
self.carbs = carbs
self.protein = protein
self.fat = fat
def __repr__(self):
return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name, self.carbs, self.protein, self.fat)
def get_nutrition(self):
"""Returns the nutritional information for the ingredient"""
return (self.carbs, self.protein, self.fat)
class Recipe(object):
"""The Recipe object containing the ingredients"""
def __init__(self, name, ingredients):
self.name = name
self.ingredients = ingredients
def get_nutrition(self):
"""Returns the nutritional information for the recipe"""
nutrition = [0, 0, 0]
for amount, ingredient in self.ingredients:
nutrition[0] += amount * ingredient.carbs
nutrition[1] += amount * ingredient.protein
nutrition[2] += amount * ingredient.fat
return nutrition
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)),
(30, Ingredient('Oil', 0, 0, 1)),
(36, Ingredient('Sugar', 1, 0, 0)),
(7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
(560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
The value of building and documenting a interface to our code is not unique to object oriented programming.
Next week we will look at creating websites as an alternative to command line programs and GUIs. Python has a rich ecosystem of web servers and frameworks for creating web applications. Importantly, the vast majority use a common interface called WSGI.
WSGI is based on a simple exchange. The example below use the wsgiref package for the web server with the application implemented without using external packages. Next week, we will look at some of the more commonly used web servers and use a web framework to develop a more substantial web project.
In [44]:
!cat Wk05-wsgi.py
In [122]:
class Ingredient(object):
"""The ingredient object that contains nutritional information"""
def __init__(self, name, *args, **kwargs):
self.name = name
self.nums = []
for a in [*args]:
if isinstance(a, dict):
for key in a.keys():
setattr(self, key, a[key])
elif isinstance(a, float):
self.nums.append(a)
if len(self.nums) in [3,4]:
for n, val in zip(['carbs', 'protein', 'fat', 'cholesterol'], self.nums):
setattr(self, n, val)
elif isinstance(a, int):
self.nums.append(a)
if len(self.nums) in [3,4]:
for n, val in zip(['carbs', 'protein', 'fat', 'cholesterol'], self.nums):
setattr(self, n, val)
else:
print('Need correct nutritional information format')
def __repr__(self):
if getattr(self, 'cholesterol', False):
return 'Ingredient({0}, {1}, {2}, {3}, {4})'.format(self.name,
self.carbs,
self.protein,
self.fat,
self.cholesterol)
else:
return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name,
self.carbs,
self.protein,
self.fat)
def get_nutrition(self):
"""Returns the nutritional information for the ingredient"""
return (self.carbs, self.protein, self.fat, self.cholestrol)
def get_name(self):
"""Returns the ingredient name"""
return self.name
class Recipe(object):
"""The Recipe object containing the ingredients"""
def __init__(self, name, *ingredients):
self.name = name
self.ingredients = [*ingredients][0]
self.number = len(*ingredients)
self.nutrition_ = {'carbs': 0, 'protein': 0, 'fat':0, 'cholesterol':0}
def __repr__(self):
return 'Recipe({0}, {1})'.format(self.name, self.ingredients)
def get_nutrition(self):
"""Returns the nutritional information for the recipe"""
#for _ in range(self.number):
nutrition = [0,0,0,0] # need to be length of dict
for amount, ingredient in self.ingredients:
# print(type(ingredient), ingredient) # test
try:
if getattr(ingredient, 'cholesterol', False):
nutrition[0] += amount * ingredient.carbs
nutrition[1] += amount * ingredient.protein
nutrition[2] += amount * ingredient.fat
nutrition[3] += amount * ingredient.cholesterol
else:
nutrition[0] += amount * ingredient.carbs
nutrition[1] += amount * ingredient.protein
nutrition[2] += amount * ingredient.fat
except AttributeError: # in case another recipe is in the ingredients (nested)
nu = ingredient.get_nutrition()
nu = [amount * x for x in nu]
nutrition[0] += nu[0]
nutrition[1] += nu[1]
nutrition[2] += nu[2]
nutrition[3] += nu[3]
return nutrition
@property
def nutrition(self):
facts = self.get_nutrition()
self.nutrition_['carbs'] = facts[0]
self.nutrition_['protein'] = facts[1]
self.nutrition_['fat'] = facts[2]
self.nutrition_['cholesterol'] = facts[3]
return self.nutrition_
def get_name(self):
return self.name
In [123]:
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)),
(30, Ingredient('Oil', 0, 0, 1)),
(36, Ingredient('Sugar', 1, 0, 0)),
(7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
(560, Ingredient('Water', 0, 0, 0))])
In [124]:
print(bread.ingredients)
# Should be roughly [(820, Ingredient(Flour, 0.77, 0.1, 0.01)), (30, Ingredient(Oil, 0, 0, 1)),
# (36, Ingredient(Sugar, 1, 0, 0)), (7, Ingredient(Yeast, 0.3125, 0.5, 0.0625)), (560, Ingredient(Water, 0, 0, 0))]
In [125]:
print(bread.nutrition)
#Should be roughly {'carbs': 669.5875, 'protein': 85.5, 'fat': 38.6375} the order is not important
In [126]:
eggs = Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258, 'fat': 0.0994, 'cholesterol': 0.00423, 'awesome':100})
#eggs = Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258, 'fat': 0.0994})
#eggs = Ingredient('Egg', 0.0077, 0.1258, 0.0994, 0.00423)
print(eggs)
In [127]:
#Points to note:
# - The different call to Ingredient, you can use isinstance or type to change the
# behaviour depending on the arguments supplied
# - Cholesterol as an extra nutrient, your implementation should accept any nutrient
# - Use of Recipe (bread) as an ingredient
basic_french_toast = Recipe('Basic French Toast', [(300, Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258,
'fat': 0.0994, 'cholesterol': 0.00423})),
(0.25, bread)])
In [128]:
print(basic_french_toast.ingredients)
# Should be roughly:
# [(300, Ingredient(Egg, 0.0077, 0.1258, 0.0994)), (0.25, Recipe(Bread, [(820, Ingredient(Flour, 0.77, 0.1, 0.01)),
# (30, Ingredient(Oil, 0, 0, 1)), (36, Ingredient(Sugar, 1, 0, 0)), (7, Ingredient(Yeast, 0.3125, 0.5, 0.0625)),
# (560, Ingredient(Water, 0, 0, 0))]))]
# Note the formatting for the Recipe object, a __repr__ method will be needed
In [129]:
print(basic_french_toast.nutrition)
# Should be roughly {'protein': 59.115, 'carbs': 169.706875, 'cholesterol': 1.2690000000000001, 'fat': 39.479375000000005}
# The order is not important
In [ ]: