82. recipe = OnlineRecipe(
"Pasta With Sausage",
"Pat Viafore",
"When I was 15, I remember ......",
6,
["Rigatoni", ..., "Basil", "Sausage"],
"First, brown the sausage ...."
)
85. Heterogeneous data
● Heterogeneous data is data that may be multiple different
types (such as str, int, list[Ingredient], etc.)
● Typically not iterated over -- you access a single field at a
time
86. # DO NOT DO THIS
recipe = {
"name": "Pasta With Sausage",
"author": "Pat Viafore",
"story": "When I was 15, I remember
....",
"number_of_servings": 6,
"ingredients": ["Rigatoni", ..., "Basil"],
"recipe": "First, brown the sausage ...."
87. # is life story the right key name?
do_something(recipe["life_story"])
# What type is recipe?
def do_something_else(recipe: dict):
# .... snip ....
88. Any time a developer has to trawl through the
codebase to answer a question about data, it
wastes time and increases frustration
89. This will create mistakes and incorrect
assumptions, leading to bugs
90. recipe: OnlineRecipe = create_recipe()
# type checker will catch problems
do_something(recipe.life_story)
def do_something_else(recipe: OnlineRecipe):
# .... snip ....
91. Use data classes to group data together and
reduce errors when accessing
95. Invariants
● Fundamental truths throughout your codebase
● Developers will depend on these truths and build
assumptions on them
● These are not universal truths in every possible system,
just your system
96.
97.
98. Invariants
● Sauce will never be put on top of other toppings
(cheese is a topping in this scenario).
● Toppings may go above or below cheese.
● Pizza will have at most only one sauce.
● Dough radius can be only whole numbers.
● The radius of dough may be only between 15 and 30 cm
103. class Pizza:
def __init__(self, radius_in_cm: int,
toppings: list[str])
assert 15 <= radius_in_cm <= 30
sauces = [t for t in toppings
if is_sauce(t)]
assert len(sauces) <= 1
self.__radius_in_cm = radius_in_cm
sauce = sauces[:1]
self.__toppings = sauce +
[t for t in toppings if not is_sauce(t)]
104. class Pizza:
def __init__(self, radius_in_cm: int,
toppings: list[str])
assert 15 <= radius_in_cm <= 30
sauces = [t for t in toppings
if is_sauce(t)]
assert len(sauces) <= 1
self.__radius_in_cm = radius_in_cm
sauce = sauces[:1]
self.__toppings = sauce +
[t for t in toppings if not is_sauce(t)]
INVARIAN
T
CHECKING
105. # Now an exception
pizza = Pizza(1000, ["Tomato Sauce",
"Mozzarella",
"Pepperoni"])
106. class Pizza:
def __init__(self, radius_in_cm: int,
toppings: list[str])
assert 15 <= radius_in_cm <= 30
sauces = [t for t in toppings
if is_sauce(t)]
assert len(sauces) <= 1
self.__radius_in_cm = radius_in_cm
sauce = sauces[:1]
self.__toppings = sauce +
[t for t in toppings if not is_sauce(t)]
107. class Pizza:
def __init__(self, radius_in_cm: int,
toppings: list[str])
assert 15 <= radius_in_cm <= 30
sauces = [t for t in toppings
if is_sauce(t)]
assert len(sauces) <= 1
self.__radius_in_cm = radius_in_cm
sauce = sauces[:1]
self.__toppings = sauce +
[t for t in toppings if not is_sauce(t)]
"Private"
Members
108. # Linters will catch this error
# Also a runtime error
pizza.__radius_in_cm = 1000
pizza.__toppings.append("Alfredo Sauce")
141. Substitutability
● Do not strengthen pre-conditions
● Do not weaken post-conditions
● Do not raise new types of exceptions
○ Looking at you, NotImplementedError
● Overridden functions almost always should call super()
152. Tips for writing Robust Python
● Don't rely on developer's memory or their ability to find all
usage in a codebase
● Communicate intent through deliberate decisions in your
codebase
● Make it hard for developers to do the wrong thing
● Make them succeed by default
● Use tooling to catch errors when they do happen
153. You have a duty to deliver value in a timely
manner
154. You have a duty to make it easy for future
collaborators to deliver value in a timely
manner