Building Functions in Python
Step-by-Step Guide to Building Functions in Python - From Naming Standards to Unit Testing.
Building Functions in Python
Writing clean, efficient, and maintainable functions is an important skill for any developer. In this article, we'll build a unit conversion function step-by-step while adhering to best practices for writing Python functions. By the end, we will have a well-written, well-documented function.
Here’s a quick look at the steps:
Use descriptive names for functions and parameters.
Keep functions small and focused on a single task.
Document your functions with docstrings.
Use type annotations for clarity and debugging.
Handle errors gracefully.
Write tests to ensure your functions work correctly.
Step 1: Use Descriptive Names
Choosing meaningful names for your functions and parameters is the first step toward writing readable code. Let's start with a basic function skeleton for converting units.
The main goal of our function is to convert values between units. That’s why we chose the simplest name for the function (convert_units
) and the parameters: value
, from_unit
, to_unit
.
def convert_units(value, from_unit, to_unit):
pass
Step 2: Keep Functions Small
The function should always perform a single task. This makes it easier to understand, test, and maintain. Our convert_units
function will focus solely on converting units.
Step 3: Document Your Functions
Try to always include docstrings to describe what your function does, its parameters and its return value. It is important to make your functions more readable, maintainable, and easier to understand.
def convert_units(value, from_unit, to_unit):
"""
Converts a value from one unit to another.
Parameters:
value (float): The value to convert.
from_unit (str): The unit to convert from.
to_unit (str): The unit to convert to.
Returns:
float: The converted value.
"""
pass
Step 4: Use Type Annotations
I like to always use Type Annotations when writing functions, at least, for parameters and the return value. Type annotations improve code readability and help with debugging.
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
"""
Converts a value from one unit to another.
Parameters:
value (float): The value to convert.
from_unit (str): The unit to convert from.
to_unit (str): The unit to convert to.
Returns:
float: The converted value.
"""
pass
Step 5: Implement the Function Logic
After the first 4 steps, we can start implementing the function logic. We'll create a dictionary to store conversion factors and use it to perform the conversion.
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
"""
Convert a value from one unit to another.
Parameters:
value (float): The value to convert.
from_unit (str): The unit to convert from.
to_unit (str): The unit to convert to.
Returns:
float: The converted value.
Raises:
ValueError: If the units are not supported.
"""
conversions = {
("meters", "feet"): 3.28084,
("feet", "meters"): 0.3048,
("kilograms", "pounds"): 2.20462,
("pounds", "kilograms"): 0.453592,
("kilometers", "miles"): 0.621371,
("miles", "kilometers"): 1.60934,
("celsius", "fahrenheit"): lambda c: (c * 9 / 5) + 32,
("fahrenheit", "celsius"): lambda f: (f - 32) * 5 / 9,
("liters", "gallons"): 0.264172,
("gallons", "liters"): 3.78541,
}
conversion_factor = conversions[(from_unit, to_unit)]
return value * conversion_factor
Step 6: Handle Errors Gracefully
It's important to handle errors and edge cases to ensure your function is robust. We will add a try-except
block to manage unsupported unit conversions gracefully.
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
"""
Convert a value from one unit to another.
Parameters:
value (float): The value to convert.
from_unit (str): The unit to convert from.
to_unit (str): The unit to convert to.
Returns:
float: The converted value.
Raises:
ValueError: If the units are not supported.
"""
conversions = {
("meters", "feet"): 3.28084,
("feet", "meters"): 0.3048,
("kilograms", "pounds"): 2.20462,
("pounds", "kilograms"): 0.453592,
("kilometers", "miles"): 0.621371,
("miles", "kilometers"): 1.60934,
("celsius", "fahrenheit"): lambda c: (c * 9 / 5) + 32,
("fahrenheit", "celsius"): lambda f: (f - 32) * 5 / 9,
("liters", "gallons"): 0.264172,
("gallons", "liters"): 3.78541,
}
try:
conversion_factor = conversions[(from_unit, to_unit)]
return value * conversion_factor
except KeyError:
raise ValueError(f"Conversion from {from_unit} to {to_unit} not supported.")
Step 7: Write Tests
Always try to write tests before you’re done with your function. Trust me, you won’t get back to it later. 😁 Tests ensure functions work correctly and help catch errors early. We'll use the unittest
module to create test cases for our convert_units
function.
class TestConvertUnits(unittest.TestCase):
def test_meters_to_feet(self):
self.assertAlmostEqual(convert_units(1, "meters", "feet"), 3.28084)
def test_feet_to_meters(self):
self.assertAlmostEqual(convert_units(1, "feet", "meters"), 0.3048)
def test_kilograms_to_pounds(self):
self.assertAlmostEqual(convert_units(1, "kilograms", "pounds"), 2.20462)
def test_pounds_to_kilograms(self):
self.assertAlmostEqual(convert_units(1, "pounds", "kilograms"), 0.5)
def test_invalid_conversion(self):
with self.assertRaises(ValueError):
convert_units(1, "meters", "pounds")
if __name__ == "__main__":
unittest.main()
The first 4 test cases check if the values returned by the function are what we anticipated. The last one checks if the function raises ValueError when incorrect parameter is passed.
Bonus Section
What if the conversions dictionary is too long? Then we would choose from the following options:
Modularize our function - In other words, divide the function into more specific ones like
convert_temperature
,convert_length
, andconvert_weight
.Create class - Encapsulate related conversions in classes to keep the code organized.
More Dynamic Approach - Store conversion factors in a database or a configuration file (e.g., JSON, YAML). This makes it easier to update and manage without changing the code.
Have questions or need further clarification? Leave a comment below or reach out directly.
✅ Thank you for reading my article on SA Space! I welcome any questions, comments, or suggestions you may have.
Keep Knowledge Flowing by following me for more content on Solutions Architecture, System Design, Data Engineering, Business Analysis, and more. Your engagement is appreciated. 🚀
🔔 You can also follow my work on LinkedIn | Substack | Threads | X