Session 14: Functions, Comments and Good Code#
Introduction#
We have previously encountered functions like print
, range
, float
, int
.
In this session we will look at what a function is in more detail, explore how we can write our own functions and when it is appropriate to use them.
We will also for the first time look at the difference between ‘good code’ and ‘bad code’, and how we can try writing good code using functions, comments and good naming practices.
Part 1: Understanding Functions#
Functions are reusable blocks of code that perform a specific task. They help in breaking down complex problems into smaller, manageable pieces. By using functions, we can avoid repetition, make our code more organized, and enhance readability.
Task 1: Defining and Calling Simple Functions#
Understanding the Basics: Before running any code, try to predict what each line will do.
Create a New Python File:
Open Visual Studio Code (VS Code) or your preferred IDE.
Create a new file named
functions_intro.py
.
Write the Following Code:
def greet(): print("Hello, welcome to the lab!") # Calling the function greet()
Run the Code:
Save the file.
Run the script and observe the output.
Analysis#
Defining a Function:
def greet():
The
def
keyword is used to define a new function namedgreet
.The “empty” parentheses
()
indicate that this function does not take any arguments.A colon
:
denotes the start of the function body - this is very similar toif
statements andfor
andwhile
loops.
Function Body:
print("Hello, welcome to the lab!")
This line is indented, indicating that it is part of the function’s body.
The function simply prints a greeting message.
Calling the Function:
greet()
The lines of code above have created the
greet
function, but they do not also run it - for that you have to call the function. This line calls thegreet
function, causing the code inside the function to execute. You only have to define a function once, after that you can call it as many times as you need.
Task 2: Functions with Parameters#
Extend the Previous Code:
Modify
functions_intro.py
to include the following:
def greet(name): print(f"Hello, {name}! Welcome to the lab!") # Calling the function with different names greet("Alice") greet("Bob")
Run the Code:
Save the file.
Run the script and observe the output.
Analysis#
Function with Parameter:
def greet(name):
The function definition is now different and shows that
greet
now takes one parameter,name
.Parameters allow functions to accept input values when called and then perform operations on those inputs.
Using the Parameter:
print(f"Hello, {name}! Welcome to the lab!")
The function uses the
name
parameter to personalize the greeting.
Calling the Function with Arguments:
greet("Alice")
greet("Bob")
When calling the function, we pass in the argument (e.g.,
"Alice"
), which gets assigned to the parametername
. Notice this rather subtle bit of terminology - can you see the difference between a parameter and an argument?!
Important
Inside functions, parameters behave a lot like variables - but you shouldn’t assign values to parameters inside functions. While not forbidden, it’s considered bad programming style. To appreciate why, consider this bit of code:
def greet(personal_data):
print(f'Hello, {personal_data['name']}! Welcome to the lab!')
personal_data['name'] = 'Donald'
personal_data = {'name':,'Claire'}
greet(personal_data)
print(f"My name is {personal_data['name']}"))
Think what it would do (or try it out!) - is this nice?
Part 2: Functions in Chemical Calculations#
Functions are particularly useful in scientific programming, where calculations often need to be repeated with different parameters.
Task 3: Calculating Moles from Mass and Molar Mass#
Create a New File:
Create a new file named
moles_calculation.py
.
Write the Following Code:
def calculate_moles(mass, molar_mass): moles = mass / molar_mass return moles # Example usage mass_of_substance = 18.0 # grams molar_mass_of_water = 18.01528 # g/mol moles = calculate_moles(mass_of_substance, molar_mass_of_water) print(f"Number of moles: {moles}")
Run the Code:
Save the file.
Run the script and observe the output.
Analysis#
In this case the function has two parameters or inputs, mass
and molar mass
.
The body of the function contains hopefully familiar calculation of number of moles, given mass and molar mass.
return moles
In previous examples, functions only had inputs.
However, here the above line means that this function returns moles as its output.
Whenever the code inside a function reaches return
statement, it immediately returns its output and the code execution inside the function stops.
Using the Function:
moles = calculate_moles(mass_of_substance, molar_mass_of_water)
Usage of the function is also slightly different in this case - here we assign the value of the variable moles
to be the output of the function calculate_moles
.
This is so that we can use the function output afterwards in the following code.
If we just called the function as previously:
calculate_moles(mass_of_substance, molar_mass_of_water)
The function would still get executed, but we would have no way of accessing the function output - it gets returned to nowhere!
Part 3: Good Code Practices#
Up till now our main concern has been writing code that works and does what we want. However, good code is not just code that works; it’s code that is readable, maintainable, and efficient. This becomes more and more important as you start writing more complex code with more moving parts. If a code is easier to understand, it is also generally easier to find and fix bugs. Finally, if you collaborate with other people in writing code, it is particularly important to create code that is easy to understand. Writing good code includes using functions appropriately, writing comments, and following good naming conventions.
Task 5: Refactoring Code for Readability#
Consider the following two code snippets:
Snippet A:
mass = 36.0
mm = 18.01528
n = mass / mm
vol = 1.0
conc = n / vol
print(conc)
Snippet B:
# Calculate the concentration of a solution
def calculate_moles(mass, molar_mass):
moles = mass / molar_mass
return moles
def calculate_concentration(moles, volume):
concentration = moles / volume
return concentration
mass_of_substance = 36.0 # grams
molar_mass_of_water = 18.01528 # g/mol
volume_of_solution = 1.0 # liters
moles = calculate_moles(mass_of_substance, molar_mass_of_water)
concentration = calculate_concentration(moles, volume_of_solution)
print(f"Concentration: {concentration} mol/L")
Try to understand what each snippet does, and how they might differ from one another.
Identify which snippet is easier to understand, more readable and more maintainable.
Discuss why using functions, comments and descriptive variable names is beneficial here.
Analysis#
Snippet A Issues:
Uses ambiguous variable names (
mm
,n
,vol
,conc
), which can be confusing.Lacks comments or explanations.
All calculations are not conceptually grouped using functions.
Snippet B Advantages:
Uses descriptive function and variable names (
calculate_moles
,calculate_concentration
,mass_of_substance
).Includes comments that explain the purpose of the code.
Breaks down the problem into reusable functions, enhancing modularity.
Improves readability and makes the code self-explanatory. Self-explanatory code is one that does not even need comments in most places, as descriptive variable and function names make understanding the code much easier.
Good Code Practices Demonstrated:
Descriptive Naming: Using clear and descriptive names for functions and variables helps others understand the code’s intent.
Comments: Brief comments explain what the code does, which is helpful for future reference or other programmers.
Modularity: Functions encapsulate specific tasks, making code reusable and easier to test.
Task 6: Adding Docstrings and Comments#
Modify
moles_calculation.py
to Include Docstrings:
def calculate_moles(mass, molar_mass):
"""
Calculate the number of moles given mass and molar mass.
Parameters:
mass (float): Mass of the substance in grams.
molar_mass (float): Molar mass of the substance in g/mol.
Returns:
float: Number of moles.
"""
moles = mass / molar_mass
return moles
def calculate_concentration(moles, volume):
"""
Calculate the concentration of a solution.
Parameters:
moles (float): Number of moles of solute.
volume (float): Volume of the solution in liters.
Returns:
float: Concentration in mol/L.
"""
concentration = moles / volume
return concentration
# Example usage
mass_of_substance = 36.0 # grams
molar_mass_of_water = 18.01528 # g/mol
volume_of_solution = 1.0 # liters
moles = calculate_moles(mass_of_substance, molar_mass_of_water)
concentration = calculate_concentration(moles, volume_of_solution)
print(f"Concentration: {concentration} mol/L")
Run the Code:
Save the file.
Run the script to ensure it still works.
Accessing Docstrings:
In the Python console, you can access the docstrings using the
help
function:
import moles_calculation help(calculate_moles) help(calculate_concentration)
VS Code (and other IDEs) will also now display helpful contextual popup windows whenever you interact with functions with docstrings!
Here you can see that VS Code has detected that we are about to use the function
calculate_moles
and has given the docstring we just provided. You can safely ignore the “Unknown” parts of the popup, but this happens because we have not actually said what the parameter or return variable types are.
Analysis#
Docstrings:
Docstrings are triple-quoted (
"""
) strings placed at the beginning of a function or module.They provide a convenient way of associating documentation with code.
Docstrings can be accessed using the
help()
function or by tools that generate documentation.While docstrings may not be necessary for small, one-author projects, they are a good idea for larger projects.
The
import
statement is not related to docstrings - we will explore what that means in the next session
Benefits:
Clarity: Clearly describes what the function does, its parameters, and its return value.
Maintenance: Helps others (and your future self) understand how to use the function.
Professionalism: Writing docstrings is a standard practice in professional coding.
Comments vs. Docstrings:
Comments are used within the code to explain specific lines or logic.
Docstrings provide documentation for functions, classes, or modules.
Task 7: Identifying Good and Bad Practices#
Review the Following Code Snippet:
def calc(x, y):
return 2 / (1 / x + 1 / y)
a = 10
b = 20
c = calc(a, b)
print(c)
Identify Issues:
What are the potential problems with this code in terms of readability and maintainability?
How could you improve it?
Rewrite the Code Applying Good Practices:
Use descriptive names.
Add comments or docstrings.
Improve readability.
Analysis#
Issues Identified:
Ambiguous Function Name:
calc
does not describe what the function calculates.Undocumented Function: No explanation of what the function does or what the parameters represent.
Vague Variable Names: Variables
a
,b
, andc
are not descriptive.Lack of Comments: No comments explaining the purpose of the code.
Improved Code:
def calculate_harmonic_mean(x, y):
"""
Calculate the harmonic mean of two numbers.
Parameters:
x (float): First number.
y (float): Second number.
Returns:
float: Harmonic mean of x and y.
"""
harmonic_mean = 2 / (1 / x + 1 / y)
return harmonic_mean
number1 = 10
number2 = 20
result = calculate_harmonic_mean(number1, number2)
print(f"The harmonic mean is {result}")
Improvements Made:
Descriptive Function Name: Renamed the function to
calculate_harmonic_mean
to reflect its purpose.Descriptive Variable Names: Changed
a
,b
, andc
tonumber1
,number2
, andresult
.Docstring: Added a docstring to explain what the function does.
Comments: Could add comments if further explanation is needed.
Output Message: Improved the print statement for clarity.
Conclusion#
In this session, we’ve explored the importance of functions in organizing and simplifying code. Functions help make code reusable, easier to read, and maintainable. We also discussed good coding practices, including:
Using Descriptive Names: Choose clear and meaningful names for variables and functions.
Writing Comments and Docstrings: Provide explanations for your code to enhance understanding.
Modularity: Break down complex problems into smaller, manageable functions.
Readability: Write code that is easy for others (and yourself) to read and understand.
By applying these practices, you improve the quality of your code, making it more professional and maintainable in the long term.
Additional Practice#
Refactor Existing Code: Look back at previous scripts you’ve written and see how you can improve them using functions and good coding practices.
Write Functions for Common Tasks: Identify repetitive tasks in your code and encapsulate them into functions.
Document Your Code: Make it a habit to write docstrings and comments as you code.