Lighthouse LabsW3D4 - Python Programming IIInstructor: Socorro E. Dominguez-Vidana |
![]() |
Overview¶
- [] Python user-defined functions (45 min)
- [] Best practices of user-defined functions (15 min)
- [] Give one or two examples of user-defined functions that incorporates various data types, error handling, and control flow structures (15 min)
- [] Sets (15 min)
- [] Survey when to use lists, dictionaries, tuples and sets (15 min)
- [] Discussion about specific Python challenge (15 min)
Motivation for today: The DRY Principle¶
Don't Repeat Yourself (The DRY Principle)
- The DRY principle encourages writing code in a way that avoids repetition.
- Every piece of knowledge or logic in your code should have a single, unambiguous, and authoritative representation.
- Prevents Redundancy: Repeated code increases the chances of introducing bugs and inconsistencies.
- Improves Maintainability: When code changes, you only need to update it in one place.
- Increases Readability: Clean, concise code is easier to understand.
Introduction to Functions¶
Mathematical Origins¶
In mathematics, a function is a relation that takes an input (or several inputs) and produces an output.
Example:
In mathematics, we represent a function typically like this:
A function $z = f(x,y)$
might take $x = 2, y = 3$
and return $f(2, 3) = 6$ equivalent to $z = 6$
- $z$ is the output.
- $x$ and $y$ are the inputs.
- $f()$ represents "what happens" in the function.
Functions in Programming¶
In programming, functions also take inputs (called arguments or parameters) and return outputs.
Just like in math, a programming function defines a relationship between the inputs and the output.
Programming functions can perform much more complex operations and can return multiple outputs or perform actions rather than just calculations.
A function is a block of organized, reusable code that is used to perform an action and may return none or multiple outputs.
Functions:
- Save time by reusing code
- Make code easier to read and maintain
- Allow for modular programming
Built-in Functions: The Starting Point¶
Python provides a set of built-in functions that are available to use without importing any additional modules.
Built-in functions offer a starting point for all programmers, providing a set of reliable tools to perform common tasks.
They encapsulate complex logic, making your code simpler and more efficient.
Use Case: Imagine you’re working in Excel, and every time you need to add up a column of numbers, you had to manually sum each cell by hand. You’d have to type out formulas for every single cell like =A1+B1+C1...
, repeating the same process over and over.
But Excel’s creators realized that tasks like summing values are extremely common, so they built the SUM
function into the software.
Instead of writing out a long formula you can use =SUM(A1:E1)
to get the total. It saves time, reduces the chance of errors, and makes your spreadsheets cleaner.
This is the same idea behind built-in functions in Python. Just like Excel provides tools like SUM to simplify common tasks, Python provides built-in functions like print()
and len()
so you don’t have to reinvent the wheel for everyday operations.
Code becomes more concise, readable, and efficient.
Where to find built-in functions? Python Documentation
globals()
{'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', 'globals()'], '_oh': {}, '_dh': [PosixPath('/Users/sedv8808/HT-Data/Instructor/LHL/LHL_Lectures/W3D4')], 'In': ['', 'globals()'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x11242b3d0>>, 'exit': <IPython.core.autocall.ZMQExitAutocall at 0x11243fcd0>, 'quit': <IPython.core.autocall.ZMQExitAutocall at 0x11243fcd0>, 'open': <function io.open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)>, '_': '', '__': '', '___': '', '__session__': '/Users/sedv8808/HT-Data/Instructor/LHL/LHL_Lectures/W3D4/W3D4_PythonProgrammingII.ipynb', '_i': '', '_ii': '', '_iii': '', '_i1': 'globals()'}
Built-in functions offer a starting point for all programmers, providing a set of reliable tools to perform common tasks. But they are not enough.
- Built-in functions cover common tasks, but every project is unique and you'll need more specific logic.
Solution: Write your own custom functions! Custom functions allow you to tailor code to your exact needs, making it easier to manage complex projects.
User-Defined Functions: Tailoring Code to Your Needs¶
How do we write a custom function?
Syntax:
def function_name(inputs):
"""
Docstring: Describes what the function does.
"""
# Set of instructions
output = inputs * 3
return output
def
: Keyword that signals the start of a function definition.function_name
: Choose a descriptive name for your function. This name should relate to what the function does. Just like variables, function names should be meaningful.inputs
: These are the values or parameters your function will take as input. They can benumbers
,strings
,lists
, or other data types. Your function will manipulate these inputs in some way to produce a result.Docstring
: The docstring describes what the function does, its inputs, and its expected output. It helps others (and yourself!) understand the purpose of the function, especially when returning to the code later. It’s enclosed in triple quotes ("""
).Set of Instructions
: Inside the function, you write the code that performs the operation. In this case, the input is multiplied by3
.return
: Keyword that specifies the output of the function. It tells Python what value the function should produce after processing the input. This value is what you'll get when you call the function elsewhere in your code. If not used, the function will returnNone
.
celsius_temperatures = [10, 25, 15, 23, 20]
I could do:
(10 * 9/5) + 32
50.0
(25 * 9/5) + 32
77.0
and one by one...
(15 * 9/5) + 32
(23 * 9/5) + 32
(20 * 9/5) + 32
Eventually, I will make a mistake... Or, what if I want to add new numbers?
To avoid errors and avoid writing one by one, let's make a function.
def celsius_to_fahrenheit(celsius):
"""
This function converts a temperature from Celsius to Fahrenheit.
Parameters:
celsius (float): Temperature in degrees Celsius.
Returns:
float: Temperature converted to degrees Fahrenheit.
"""
fahrenheit = (celsius * 9/5) + 32
return fahrenheit # Mention what happens if there is no return statement.
celsius_to_fahrenheit(18)
64.4
fahrenheit_temperatures = [celsius_to_fahrenheit(temperature) for temperature in celsius_temperatures]
fahrenheit_temperatures
[50.0, 77.0, 59.0, 73.4, 68.0]
Exercise: Write a Function to Square a Number $f(x)=x^2$ (7 min)
Introduction to Error Handling¶
Taylor, M. (2011, August 3). Fun with Celsius. Mormon Cartoonist.
Errors can arise in unexpected ways:
- wrong inputs
- impossible values
- logical mistakes
We need to handle these bugs before they turn simple programs into a nightmare.
celsius_to_fahrenheit(-500)
-868.0
That would be... beyond freezing...
Potential Errors in Temperature Conversion¶
Let's think about the kinds of issues that might pop up when converting temperatures:
- **Invalid Input Type***: What if someone enters text instead of a number?
- Impossible Temperatures: For example, temperatures below absolute zero (-273.15°C) don’t physically exist.
- Incorrect Units: If someone’s working in Kelvin, we’ll get wrong results unless we handle it properly.
Adding assert
for Input Validation¶
- Let's make sure that the input is a number. If someone tries to pass a string like "twenty," the program will break.
- We can use an
assert
statement. It checks that the condition isTrue
, otherwise, we pass a message to the user:
assert isinstance(celsius, (int, float)), "Input must be a numeric value."
assert isinstance(2, (int, float)), "Input must be a numeric value."
def celsius_to_fahrenheit(celsius):
"""
This function converts a temperature from Celsius to Fahrenheit.
Parameters:
celsius (float): Temperature in degrees Celsius.
Returns:
float: Temperature converted to degrees Fahrenheit.
"""
assert isinstance(celsius, (int, float)), "Input must be a numeric value."
fahrenheit = (celsius * 9/5) + 32
return fahrenheit
celsius_to_fahrenheit(4)
39.2
#celsius_to_fahrenheit('4')
Handling Impossible Temperatures with raise
¶
- Next, let's handle logically impossible temperatures. In physics, we know temperatures can’t go below absolute zero (
-273.15°C
). If someone tries to convert-300°C
, we need to catch that:
raise ValueError("Temperature cannot be below absolute zero (-273.15°C).")
Common Errors include:
- ValueError: A function receives an argument of the correct type but with an inappropriate value.
- TypeError: An operation or function is applied to an object of an inappropriate type.
- KeyError: A dictionary key is not found.
- IndexError: An index that is out of range in a list or another indexable collection.
- Others: See documentation
- Custom Errors
#raise ValueError("Temperature cannot be below absolute zero (-273.15°C).")
def celsius_to_fahrenheit(celsius):
"""
This function converts a temperature from Celsius to Fahrenheit.
Parameters:
celsius (float): Temperature in degrees Celsius.
Returns:
float: Temperature converted to degrees Fahrenheit.
"""
assert isinstance(celsius, (int, float)), "Input must be a numeric value."
if celsius < -273.15:
raise ValueError("Temperature cannot be below absolute zero (-273.15°C).")
fahrenheit = (celsius * 9/5) + 32
return fahrenheit
#celsius_to_fahrenheit(-300)
Handling Unexpected Encounters with try-except
¶
- When creating functions, try to anticipate how users might interact with them.
- Even a well-designed function can encounter unexpected inputs or conditions that lead to errors.
- Sometimes, you do not want the software to crash, instead you would want the code to continue running.
- By wrapping our function call in a
try
andexcept
block, we can handle these issues gracefully, ensuring that our program remains robust and user-friendly.
Imagine a new set of temperatures:
temperatures = [10, -300, 'twenty', 20]
converted_temperatures = []
for temp in temperatures:
try:
converted_temp = celsius_to_fahrenheit(temp)
except (AssertionError, ValueError) as e:
print(f"Error processing temperature {temp}: {e}")
converted_temp = None
except Exception as e:
print(f"An unexpected error occurred with temperature {temp}: {e}")
converted_temp = None
converted_temperatures.append(converted_temp)
Error processing temperature -300: Temperature cannot be below absolute zero (-273.15°C). Error processing temperature twenty: Input must be a numeric value.
converted_temperatures
[50.0, None, None, 68.0]
The way the function is written, would not allow me to do list comprehension. But I could modify the function with try-except
chunks so that I could use list comprehension too:
def celsius_to_fahrenheit(celsius):
"""
This function converts a temperature from Celsius to Fahrenheit.
Parameters:
celsius (float): Temperature in degrees Celsius.
Returns:
float: Temperature converted to degrees Fahrenheit.
"""
try:
assert isinstance(celsius, (int, float)), "Input must be a numeric value."
if temp < -273.15:
raise ValueError("Temperature cannot be below absolute zero (-273.15°C).")
fahrenheit = (celsius * 9/5) + 32
return fahrenheit
except AssertionError as e:
print(f"Error: {e}")
return None
except ValueError as e:
print(f"Error: {e}")
return None
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
temperatures = [10, -300, 'twenty', 20]
converted_temperatures = [celsius_to_fahrenheit(temperature) for temperature in temperatures]
Error: Input must be a numeric value.
converted_temperatures
[50.0, -508.0, None, 68.0]
Sets¶
A set is an unordered collection of unique items. Defined using curly braces
{}
or theset()
function.
Characteristics of Sets¶
- Unordered: The items in a set do not have a specific order.
- Unique Elements: Sets automatically eliminate duplicate entries.
- Mutable: You can add or remove items from a set after its creation.
- Support for Mathematical Operations: Sets can be used for union, intersection, and difference operations.
my_set = {1, 2, 3}
another_set = set([3, 4, 5, 3])
another_set
{3, 4, 5}
Set Operations¶
# Adding elements
my_set.add(5)
my_set
{1, 2, 3, 5}
# Removing elements
my_set.remove(3)
my_set
{1, 2, 5}
# Mathematical Operations
set_a = {1, 2, 3}
set_b = {3, 4, 5}
union = set_a | set_b
union
{1, 2, 3, 4, 5}
intersection = set_a & set_b
intersection
{3}
difference = set_a - set_b
difference
{1, 2}
Uses¶
- Eliminating duplicates.
- Sets provide faster membership tests than lists.
- Simplify code and improve performance when doing mathematical operations.
- Consider using sets when dealing with unique items, membership testing, or mathematical operations to improve performance.
Challenge Time¶
Objective: Create a function that takes two dictionaries
. One dictionary will represent the items currently in a pantry and another representing the ingredients required for a recipe.
The function should return a shopping list containing the items that need to be purchased.
pantry = {'eggs': 6, 'milk': 1, 'flour': 0.5, 'sugar': 0.2}
pantry
{'eggs': 6, 'milk': 1, 'flour': 0.5, 'sugar': 0.2}
recipe = {'eggs': 3, 'milk': 0.5, 'flour': 1, 'butter': 0.1,
'sugar': 0.3 }
recipe
{'eggs': 3, 'milk': 0.5, 'flour': 1, 'butter': 0.1, 'sugar': 0.3}
def shopping_list_creator(pantry, recipe):
"""
"""
shopping_list = {}
# Iterate over the recipe items
# Calculate the remaining quantity needed
# If the item is in the pantry, calculate the difference
# If more is needed, add it to the shopping list
return None
Discussion:
Think of 3 raise
and assert
statements you could add to the function.