SlideShare a Scribd company logo
User-Defined Types.pdf
User-Defined Types.pdf
Days?
Days?
Months?
Days?
Months?
Years?
What about Decades?
User-Defined Types.pdf
Code will live a long time
Code will outlive your time working on it
Code will become legacy
Legacy Code
A codebase where you do not have direct communication with
the original authors
Why does this matter?
YOU
YOU
Tasks completed in
a month
Fast-forward a few years........
?
Future
Collaborator
Tasks completed in
a month
Future
Collaborator
Tasks completed in
a month
Future
Collaborator
Tasks completed in
a month
You have a duty to deliver value in a timely
manner
You have a duty to make it easy for future
collaborators to deliver value in a timely manner
Make it easy for your future collaborator
Do you want future collaborators to thank you
for your foresight ........
.......or curse your name?
Robust Python
Building a Vocabulary with User-Defined Types
Patrick Viafore
HSV.py Lunchtime Talk
4/18/2023
A Robust Codebase
A robust codebase is full of code that is clean and
maintainable, and is resilient and error-free in spite of
constant change
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
How do you build your codebase without
knowing what's in the future?
User-Defined Types.pdf
Legacy Code
A codebase where you do not have direct communication with
the original authors
Your codebase is your most effective tool of
communication and collaboration with other
developers
Software Engineering is simultaneously
archaeology and time-travel
Let your codebase be your Rosetta Stone
Communicating
Intent
Reduce Errors
Improve Future
Development
https://learning.oreilly.com/get-learning/?code=ROBUSTP21
Typechecking User Defined Types
Extensibility Building a Safety Net
Typechecking User Defined Types
Extensibility Building a Safety Net
User-defined types are a way for you to define
your own vocabulary
The abstractions you choose communicate to
the future
def print_receipt(
order: Order,
restaurant: tuple[str, int, str]):
total = (order.subtotal *
(1 + tax[restaurant[2]]))
print(Receipt(restaurant[0], total))
def print_receipt(
order: Order,
restaurant: tuple[str, int, str]):
total = (order.subtotal *
(1 + tax[restaurant[2]]))
print(Receipt(restaurant[0], total))
def print_receipt(
order: Order,
restaurant: Restaurant):
total = (order.subtotal *
(1 + tax[restaurant.city]))
print(Receipt(restaurant.name, total))
User-defined types unify mental models
User-defined types make it easier to reason
about a codebase
User-defined types make it harder to make
errors
I want to focus on the "why" we use user-defined
types, not "how" to create them
Enumerations
User-Defined Types.pdf
MOTHER_SAUCES = ("Béchamel",
"Velouté",
"Espagnole",
"Tomato",
"Hollandaise")
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce(MOTHER_SAUCE[0],
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
# What was MOTHER_SAUCE[0] again?
create_daughter_sauce(MOTHER_SAUCE[0],
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce(MOTHER_SAUCE[0],
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce("Bechamel",
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce("BBQ Sauce",
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce("BBQ Sauce",
["Onion"])
Wrong
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce("Bechamel",
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce("Béchamel",
["Onion"])
Strings can be anything
Restrict your choices with enumerations
from enum import Enum
class MotherSauce(Enum):
BÉCHAMEL = "Béchamel"
VELOUTÉ = "Velouté"
ESPAGNOLE = "Espagnole"
TOMATO = "Tomato"
HOLLANDAISE = "Hollandaise"
MotherSauce.BÉCHAMEL
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce(MOTHER_SAUCE[0],
["Onion"])
def create_daughter_sauce(
mother_sauce: str,
extra_ingredients: list[str]):
# ...
create_daughter_sauce(MOTHER_SAUCE[0],
["Onion"])
def create_daughter_sauce(
mother_sauce: MotherSauce,
extra_ingredients: list[str]):
# ...
create_daughter_sauce(MotherSauce.BÉCHAMEL,
["Onion"])
Catch mistakes with static analysis
Use enumerations to prevent collaborators from
using incorrect values
Use enumerations to simplify choices
Prevent bugs
What about composite data?
Data Classes
Author's Name
Recipe
Ingredient List
Author's Life
Story
# of Servings
Recipe Name
Online Recipe
Author's Name
Recipe
Ingredient List
Author's Life
Story
# of Servings
Recipe Name
Data classes represent a relationship between
data
@dataclass
class OnlineRecipe:
name: str
author_name: str
author_life_story: str
number_of_servings: int
ingredients: list[Ingredient]
recipe: str
recipe = OnlineRecipe(
"Pasta With Sausage",
"Pat Viafore",
"When I was 15, I remember ......",
6,
["Rigatoni", ..., "Basil", "Sausage"],
"First, brown the sausage ...."
)
recipe.name
>>> "Pasta With Sausage"
recipe.number_of_servings
>>> 6
Data classes represent heterogeneous data
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
# 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 ...."
}
# is life story the right key name?
put_on_top_of_page(recipe["life_story"])
# What type is recipe?
def double_recipe(recipe: dict):
# .... snip ....
Any time a developer has to trawl through the
codebase to answer a question about data, it
wastes time and increases frustration
This will create mistakes and incorrect
assumptions, leading to bugs
recipe: OnlineRecipe = create_recipe()
# type checker will catch problems
put_on_top_of_page(recipe.life_story)
def double_recipe(recipe: OnlineRecipe):
# .... snip ....
Use data classes to group data together and
reduce errors when accessing
You communicate intent and prevent future
developers from making errors
Data classes aren't appropriate for all
heterogeneous data
Invariants
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
User-Defined Types.pdf
User-Defined Types.pdf
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
@dataclass
class Pizza:
radius_in_cm: int
toppings: list[str]
pizza = Pizza(15, ["Tomato Sauce",
"Mozzarella",
"Pepperoni"])
# THIS IS BAD!
pizza.radius_in_cm = 1000
pizza.toppings.append("Alfredo Sauce")
Classes
@dataclass
class Pizza:
radius_in_cm: int
toppings: list[str]
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)]
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)]
INVARIANT
CHECKING
# Now an exception
pizza = Pizza(1000, ["Tomato Sauce",
"Mozzarella",
"Pepperoni"])
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)]
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
# Linters will catch this error
# Also a runtime error
pizza.__radius_in_cm = 1000
pizza.__toppings.append("Alfredo Sauce")
Classes create invariants that developers cannot
easily modify
Classes must always preserve these invariants
class Pizza:
# ... snip ...
def add_topping(self, topping: str):
if is_sauce(topping) and self.has_sauce():
raise TooManySaucesError()
if is_sauce(topping):
self.__toppings.insert(0, topping)
else:
self.__toppings.append(topping)
Give future collaborators solid classes to reason
upon
Classes allow you to group inter-related data
and preserve invariants across their lifetime
Creating User-Defined Types
The abstractions you choose communicate to
the future
API
Methods
@dataclass
class OnlineRecipe:
name: str
author_name: str
author_life_story: str
number_of_servings: int
ingredients: list[Ingredient]
recipe: str
@dataclass
class OnlineRecipe:
...
def condense(self):
# ...
def scale_to(self, servings: int)
# ...
The Paradox of Code Interfaces
You have one chance to get your interface
right....
.....but you won't know it's right until it's used.
What happens if you don't get it right
When it all goes wrong
● Duplicated functionality
● Broken mental models
● Reduced testing
User-Defined Types.pdf
Test-Driven Development
Test-Driven Development
Test-Driven Development
README-Driven Development
Usability Testing
Natural Design
User-Defined Types.pdf
User-Defined Types.pdf
User-Defined Types.pdf
Magic Methods
@dataclass
class Ingredient:
name: str
units: ImperialMeasure # cup, tbsp, or tsp
quantity: int
class Recipe:
...
def add_ingredient(self, ing: Ingredient):
if self.contains(ing):
???
else:
self.ingredients.append(ingredients)
class Recipe:
...
def add_ingredient(self, ing: Ingredient):
matched = self._find_ingredient(ing)
if matched:
matched += ing
else:
self.ingredients.append(ingredients)
class Ingredient:
def __add__(self, rhs: Ingredient):
assert self.name == rhs.name
converted = rhs.convert(self.units)
return Ingredient(self.name,
self.units,
(self.quantity +
converted.quantity))
User-Defined Types.pdf
Context Managers
def reserve_table(table: Table):
assert not table.is_reserved()
table.hold()
wait_for_user_confirmation()
if is_confirmed():
table.reserve()
table.remove_hold()
def reserve_table(table: Table):
assert not table.is_reserved()
table.hold()
wait_for_user_confirmation()
if is_confirmed():
table.reserve()
table.remove_hold()
def reserve_table(table: Table):
assert not table.is_reserved()
with table.hold():
wait_for_user_confirmation()
if is_confirmed():
table.reserve()
from contextlib import contextmanager
class Table:
...
@contextmanager
def hold(self):
...
@contextmanager
def hold(self):
# .. hold logic ...
try:
yield
finally:
table.remove_hold()
Remove friction in your code
Sub-typing
Sub-typing is a relationship between types
A sub-type has all the same behaviors as a
super-type (it may also customize some
behaviors)
Inheritance
class Rectangle:
def __init__(self, height: int, width: int):
self.__height = height
self.__width = width
def set_width(self, width: int):
self.__width = width
def set_height(self, height: int):
self.__height = height
# ... snip getters ...
class Square(Rectangle):
def __init__(self, side_length: int):
self.set_height(side_length)
def set_height(self, side_length: int):
self.__height = side_length
self.__width = side_length
def set_width(self, side_length: int):
self.__height = side_length
self.__width = side_length
What I've just shown you has a very subtle error.
User-Defined Types.pdf
User-Defined Types.pdf
FOOD_TRUCK_AREA_SIZES = [
Rectangle(1, 20),
Rectangle(5, 5),
Rectangle(20, 30)
]
Is a square a rectangle?
User-Defined Types.pdf
Yes, a square is a rectangle (geometrically
speaking)
Is a square substitutable for a rectangle?
User-Defined Types.pdf
FOOD_TRUCK_AREA_SIZES = [
Rectangle(1, 20),
Rectangle(5, 5),
Rectangle(20, 30)
]
FOOD_TRUCK_AREA_SIZES = [
Rectangle(1, 20),
Square(5),
Rectangle(20, 30)
]
What can go wrong?
def double_food_truck_area_widths():
for ft_shape in FOOD_TRUCK_AREA_SIZES:
old_size = ft_shape.get_width()
ft_shape.set_width(old_size * 2)
def double_food_truck_area_widths():
for ft_shape in FOOD_TRUCK_AREA_SIZES:
old_size = ft_shape.get_width()
# What happens when this is a square?
ft_shape.set_width(old_size * 2)
def double_food_truck_area_widths():
for ft_shape in FOOD_TRUCK_AREA_SIZES:
old_size = ft_shape.get_width()
old_height = ft_shape.get_height()
ft_shape.set_width(old_size * 2)
# Is this a reasonable assert?
assert ft_shape.get_height() == old_height
Developers will write code based on the
constraints of the superclass
Do not let subclasses violate those constraints
Someone changing a super-class should not
need to know about all possible sub-classes
Liskov Substitution Principle
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()
Inheritance
Is-A
Is-A
Can-Substitute-For-A
Types of sub-typing
● Inheritance
● Duck Typing
● Protocols
● Plug-ins
● etc.
How you subtype will influence how easy it is to
make errors as code changes
Why did I give this talk?
Who Am I?
Principal Software Engineer
Cloud Software Group
Owner of Kudzera, LLC
Author of Robust Python
Organizer of HSV.py
Contact Me
@PatViaforever
pat@kudzera.com
User-Defined Types.pdf

More Related Content

PPTX
Robust Python.pptx
PPTX
Week 02 Lesson 02 Recursive Data Definitions
PDF
Example mapping workshop
PDF
Boost Productivity with 30 Simple Python Scripts.pdf
PDF
Intro to Sociotechnical Architecture: co-designing technical & organizational...
PPTX
Coffee scriptisforclosers nonotes
PPTX
Markethive Powerful Social Neworking, Viral Blogging System And So Much More
PDF
How to Sharpen Your Investigative Analysis with PowerPivot
Robust Python.pptx
Week 02 Lesson 02 Recursive Data Definitions
Example mapping workshop
Boost Productivity with 30 Simple Python Scripts.pdf
Intro to Sociotechnical Architecture: co-designing technical & organizational...
Coffee scriptisforclosers nonotes
Markethive Powerful Social Neworking, Viral Blogging System And So Much More
How to Sharpen Your Investigative Analysis with PowerPivot

Similar to User-Defined Types.pdf (20)

PDF
SoTWLG Intro to Code Bootcamps 2016 (Roger Nesbitt)
PPTX
Text Analysis with Machine Learning
PPTX
Spreadsheets are code
PDF
Code Quality Makes Your Job Easier
PDF
Scalable Design Systems with Sketch
PPTX
Xomia_20220602.pptx
PPTX
Software fundamentals
PDF
Python: The Dynamic!
PDF
Lean UX Without Skimping on the Meat
PDF
Python for scientific computing
PDF
Types Working for You, Not Against You
KEY
Charming python
PPS
Introduction to Bootstrap: Design for Developers
PDF
Extreme Salesforce Data Volumes Webinar (with Speaker Notes)
PDF
Tbjsphx918
PPTX
EN Intro to Recursion by Slidesgo para tesis.pptx
PDF
The Final Programming Project
PPTX
Python 101: Python for Absolute Beginners (PyTexas 2014)
PDF
Introduction to programming - class 2
PDF
Actions On Google - GDD Europe 2017
SoTWLG Intro to Code Bootcamps 2016 (Roger Nesbitt)
Text Analysis with Machine Learning
Spreadsheets are code
Code Quality Makes Your Job Easier
Scalable Design Systems with Sketch
Xomia_20220602.pptx
Software fundamentals
Python: The Dynamic!
Lean UX Without Skimping on the Meat
Python for scientific computing
Types Working for You, Not Against You
Charming python
Introduction to Bootstrap: Design for Developers
Extreme Salesforce Data Volumes Webinar (with Speaker Notes)
Tbjsphx918
EN Intro to Recursion by Slidesgo para tesis.pptx
The Final Programming Project
Python 101: Python for Absolute Beginners (PyTexas 2014)
Introduction to programming - class 2
Actions On Google - GDD Europe 2017
Ad

More from Patrick Viafore (12)

PPTX
Extensible Python: Robustness through Addition - PyCon 2024
PDF
The Most Misunderstood Line In Zen Of Python.pdf
PDF
Tip Top Typing - A Look at Python Typing
PPTX
RunC, Docker, RunC
PDF
DevSpace 2018 - Practical Computer Science: What You Need To Know Without Th...
PPTX
Controlling Raspberry Pis With Your Phone Using Python
PDF
C++17 not your father’s c++
PPTX
Building a development community within your workplace
PPTX
Lambda Expressions in C++
PPTX
BDD to the Bone: Using Behave and Selenium to Test-Drive Web Applications
PPTX
Hsv.py Lightning Talk - Bottle
PPTX
Controlling the browser through python and selenium
Extensible Python: Robustness through Addition - PyCon 2024
The Most Misunderstood Line In Zen Of Python.pdf
Tip Top Typing - A Look at Python Typing
RunC, Docker, RunC
DevSpace 2018 - Practical Computer Science: What You Need To Know Without Th...
Controlling Raspberry Pis With Your Phone Using Python
C++17 not your father’s c++
Building a development community within your workplace
Lambda Expressions in C++
BDD to the Bone: Using Behave and Selenium to Test-Drive Web Applications
Hsv.py Lightning Talk - Bottle
Controlling the browser through python and selenium
Ad

Recently uploaded (20)

PDF
Electronic commerce courselecture one. Pdf
PPTX
Big Data Technologies - Introduction.pptx
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
A comparative analysis of optical character recognition models for extracting...
PDF
Machine learning based COVID-19 study performance prediction
PPTX
Spectroscopy.pptx food analysis technology
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Approach and Philosophy of On baking technology
PPTX
sap open course for s4hana steps from ECC to s4
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
Machine Learning_overview_presentation.pptx
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Electronic commerce courselecture one. Pdf
Big Data Technologies - Introduction.pptx
The AUB Centre for AI in Media Proposal.docx
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
A comparative analysis of optical character recognition models for extracting...
Machine learning based COVID-19 study performance prediction
Spectroscopy.pptx food analysis technology
Per capita expenditure prediction using model stacking based on satellite ima...
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Spectral efficient network and resource selection model in 5G networks
The Rise and Fall of 3GPP – Time for a Sabbatical?
Approach and Philosophy of On baking technology
sap open course for s4hana steps from ECC to s4
Network Security Unit 5.pdf for BCA BBA.
Building Integrated photovoltaic BIPV_UPV.pdf
Machine Learning_overview_presentation.pptx
Review of recent advances in non-invasive hemoglobin estimation
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025

User-Defined Types.pdf