W3 - Good Programming Practices - Course Notes
W3 - Good Programming Practices - Course Notes
Week 3 Overview
This week we will introduce you to a set of principles for writing “good” code.
Writing code from scratch is often very time consuming. You should always try to make your
code reusable, so that the code can be used to solve similar problems with only minimal extra
effort. You should also try to reuse existing code where possible, rather than write highly-
specialised new code.
Writing code that follows the good practice principles that we discuss this week may sometimes
take longer than writing poor code, at least initially. However, it is a small investment of time
that can save you many hours later.
The principles that we discuss this week include:
• Writing code that can be reused or adapted easily.
• Using functions where appropriate to make your code modular and hide complexity.
• Documenting your code well enough that anyone can understand the general process
that it uses.
• Testing your code to ensure it produces the correct outputs
1
ENG1014 Engineering Numerical Analysis Course Notes
3.1. Hardcoding
Consider the following example:
At first glance, it might seem that the second example is more efficient; it’s only one line of code
instead of two. But what happens if we decide that we actually want to use x = 5? We would have
to go back and change four individual copies of that number. In contrast, we would only have to
change one copy of that number in the first example.
Chances are good that we might miss one copy, especially if the copies are not all in the one
line like they are here but are spread throughout a long piece of code. This would then cause a
mysterious problem in our code, which might take a long time to correct.
“Hardcoding” is the practice of writing a value directly into your code, rather than saving it as a
variable and then using the variable in any later calculations. It’s considered to be bad practice,
because it makes it more difficult to change your code later.
Any number that might be used more than once should be defined as a variable. Once
defined, it should be used by calling its name.
2
ENG1014 Engineering Numerical Analysis Course Notes
3.2. Functions
In 1.6. Python Built-in Functions, we introduced functions as groups of statements that together
perform a single task. We also introduced several built-in functions that are available in every
Python installation.
Python also allows you to create your own functions. Functions are modular as they can reuse
a pattern of code on different input values. This section will describe how to create and use
custom functions in your code.
In this example, we are calculating sin(𝑥) × cos (𝑥) × tan(𝑥) multiple times and just changing
the value of 𝑥 (i.e. changing 𝑥 to 𝑦, 𝑦 2, 𝑦 3, etc.). This is time consuming to do, but even more
time consuming if later on we want to change the equation slightly – e.g. if we found out that the
equation should actually be sin2 (𝑥 ) × tan (𝑥) . Imagine if you’d already written the whole
equation 100 times in your code!
We should always try to write code in a way that avoids this situation, by not repeating the same
code more than necessary. You can see that this is similar to hardcoding a variable, as we
discussed in 3.1. Hardcoding, but in this case we are hardcoding the function itself.
In this case, we can create a function to compute sin(𝑥) × cos (𝑥) × tan(𝑥). This function will
accept one input (𝑥) and return the result of sin(𝑥) × cos (𝑥) × tan(𝑥) as the output. We will use
this example to illustrate how functions can be useful.
3
ENG1014 Engineering Numerical Analysis Course Notes
• function_name: name of the function, follows the same naming rules as variables
• arguments: as many inputs as you'd like, separated by commas. Note: Functions can
have no arguments. In that case, the parenthesis would just be empty.
• In theory, the return value does not need to be declared. In that case, Python will return
a None value, and you won't be able to use any results that have been calculated. It is
best practice to always declare the return value, even if the return value is None.
For our sin(𝑥) × cos (𝑥) × tan(𝑥) example, the sct() function below will accept one argument
(𝑥), calculate the desired output (sin(𝑥) × cos (𝑥) × tan(𝑥)), and return the result as the output.
def sct(in_val):
out_val = np.sin(in_val) * np.cos(in_val) * np.tan(in_val)
return out_val
If you provide too few or too many arguments, the function won’t run.
4
ENG1014 Engineering Numerical Analysis Course Notes
Unlike our sin(𝑥) × cos (𝑥) × tan(𝑥) example, when calling conecup_calc(), multiple
arguments need to be provided.
There are two methods of calling functions with multiple arguments: positional arguments and
keyword arguments. Additionally, optional arguments provide another method of both defining
and calling functions with multiple arguments.
Positional arguments
By default, when a function is called, the arguments are assigned to variables in the same order
that they are listed in the function definition.
For example:
• We can call conecup_calc(1,2) to calculate A with V = 1 and r = 2
• Calling conecup_calc(2,1) will return a different value for A than calling
conecup_calc(1, 2)
Keyword arguments
We can specify which arguments we are defining by specifying the name of the argument. If all
the names are specified, it does not matter which order the arguments are called in. However,
all positional arguments must be listed before any keyword arguments.
For example:
• We can call conecup_calc(V = 1, r = 2) to calculate A with V = 1 and r = 2
• We can call conecup_calc(r = 2, V = 1) to calculate A with V = 1 and r = 2
• Calling conecup_calc(V = 1, 2) will return an error, as all positional arguments
must be listed before any keyword arguments
5
ENG1014 Engineering Numerical Analysis Course Notes
Optional arguments
In some cases, a function might have been written in such a way that some arguments have
default values. Since these arguments have default values, these arguments are thus optional.
If you do not specify a value for that argument, the default value will be used. You will have seen
several functions with optional arguments in previous weeks.
For our conical paper cup example, the conecup_calc_2() function below accepts V and r as
arguments and returns A. However, this version has r as an optional argument with a default
value of 2:
def conecup_calc_2(V, r = 2):
h = 3 * V / (np.pi * r**2)
A = np.pi * r * np.sqrt(r**2 + h**2)
return A
For example:
• We can call conecup_calc(1) to calculate A with V = 1 and r = 2
• We can call conecup_calc(1,1) to override the default value and calculate A with V =
1 and r = 1
The call above will create the values c and d, with values of 12.66… and h=0.24…, respectively.
There are a couple of incorrect methods of calling functions with multiple outputs that result in
the outputs being saved incorrectly. When you call functions with multiple outputs, ensure you
provide the correct number of variables to save those outputs to, in the correct order.
Refer to Example 1 from the W3 – Good Programming Practices – Course Notes Examples
6
ENG1014 Engineering Numerical Analysis Course Notes
Refer to Example 2 from the W3 – Good Programming Practices – Course Notes Examples
7
ENG1014 Engineering Numerical Analysis Course Notes
Complete Practice Questions 1-4 from the W3 – Good Programming Practices – Course
Notes Practice Questions
8
ENG1014 Engineering Numerical Analysis Course Notes
• You can have multiple inputs and outputs, but only one expression.
• Most often, this type of function is used to represent mathematical expressions.
So far, it seems like the only differences between lambda functions and regular functions is a
list of things that lambda functions can’t do – they can’t be more than one line of code and can’t
be used in other files. So why can’t we define all our functions the normal way, using def?
The special power of a lambda function is that it allows you to define a function (A), and then
pass that function to another function (B), without specifying any values for the variables used
by A. In other words, the function itself can be treated like a variable. It can often be very
convenient to define lambda functions and then use them inside of calling other functions.
We will find this ability very useful later in the course when we discuss numerical methods, but
it can also be very helpful when representing mathematical expressions that rely on other
mathematical expressions.
For example, our conecup_calc() example could be defined like this:
conecup_h = lambda V, r: 3 * V / (np.pi * r**2)
conecup_A = lambda V, r: np.pi * r * np.sqrt(r**2 + conecup_h(V,r)**2)
Refer to Example 4 from the W3 – Good Programming Practices – Course Notes Examples
Complete Practice Questions 5-8 from the W3 – Good Programming Practices – Course
Notes Practice Questions
9
ENG1014 Engineering Numerical Analysis Course Notes
To create a module, save the code you want in a file called module_name.py. The name of the
module will be the same as the name of the file. This name is used both to import the module,
and to access its contents.
It is important to note that whenever you import a file, you will run all the code in that file, in the
same way as if you just run that file on its own. This is how all the functions and variables that
you have defined in a different file can be brought into the file where you want to use them.
However, this also means that if you had some extra code that was not one of the function or
variable definitions you wanted to bring over, you would run all that code too. In general, you
should write modules that contain only the function and variable definitions you want to bring
over to other files.
We will now begin building the eng1014 module that you will need for the rest of semester. We
will also introduce a new “signpost” in the course notes to point you to the instructions you will
need to follow the instructions to create your own eng1014 module. For week 3, this is a file
called “W3 – Good Programming Practices – ENG1014 Module Instructions.ipynb”, which can
be opened using JupyterLab.
Complete Steps 1-3 from the W3 – Good Programming Practices – ENG1014 Module
Instructions
Complete Practice Questions 9-10 from the W3 – Good Programming Practices – Course
Notes Practice Questions
3.5. Documentation
In 2.1.7. Effectively communicating printed results, we mentioned that it is vital to follow good
practices to make your methods (code and written workings) able to be understood, used, and
modified by yourself and other people. Documentation is the process of writing about your
code, so that it is clear what your code does – how it works, what it is for and why it is written the
way it is.
There are two forms of documentation in Python:
• Docstrings – how to use code
• Comments – why and how code works
3.5.1. Docstrings
Docstrings are the documentation that explains how to use code and are for the users of your
code. In particular, docstrings are the documentation of functions. Docstrings start and ends
with triple quotes ("""). A docstring is placed immediately after the function definition.
All user-defined functions should have a docstring that includes the following information:
• Description of what the function does
• Description of the input argument and outputs
10
ENG1014 Engineering Numerical Analysis Course Notes
Syntax:
def <function_name>(<argument(s)>)
"""
<function description>
Args:
<arg 1>: <argument 1 description>
<arg 2>: <argument 2 description>
Returns:
<return value 1>: <return value 1 description>
<return value 2>: <return value 2 description>
"""
return <return value(s)>
For example, our sct() example should include the following function documentation:
def sct(inp_val):
"""
The sct function takes a number input and calculates the product
of the sine, cosine and tangent of the input
Args:
in_val: Input angle in radians
Returns:
out_val: The product of the sin, cos and tan values of in_val
"""
outp_val = np.sin(in_val) * np.cos(in_val) * np.tan(in_val)
return out_val
11
ENG1014 Engineering Numerical Analysis Course Notes
Additionally, there is often a way to read the function documentation in your IDE, as your IDE
will use the docstring to automatically create a tooltip with information about the function. In
JupyterLab, place your cursor on the function name and then type shift+tab. In Spyder, hover
your cursor over the function name, and click on the tooltip to expand for additional help.
1
https://docs.python.org/3/library/functions.html#pow
12
ENG1014 Engineering Numerical Analysis Course Notes
The official documentation that you will need for this unit can be found here:
• Python
• NumPy
• Matplotlib
While the official documentation is always good to understand, sometimes it is easier or more
helpful to look at unofficial sources, or search the internet to find what you want. Many websites
exist that document not only individual functions or libraries, but also specific problems that
you may have.
2
https://numpy.org/doc/stable/reference/generated/numpy.exp.html
13
ENG1014 Engineering Numerical Analysis Course Notes
effort studying how it works. This section will describe some common factors we generally
consider when discussing the documentation of a script file.
When writing any script, you should consider organising the script so that any user or other
programmers can conveniently read through the script and understand the purpose and
application of the code. Examine Figure 3.3 and ask yourself how much of the code you can
understand, and what features help/hinder your understanding.
Comments should be written to explain the overall purpose of the code, rather than the
mechanics of how it works. You can add comments to provide reference information that you
would use in your code. Comments are also an excellent option to describe limitations and
mention needed improvements of any script version. For complicated programs, comments
can be just as critical as the code itself – especially when working within a team or when the
code must be checked by others.
14
ENG1014 Engineering Numerical Analysis Course Notes
Figure 3.4 - Example of improved documentation of a script (note that this could still be
improved significantly!)
15
ENG1014 Engineering Numerical Analysis Course Notes
Here are some guidelines on using comments to provide proper documentation of your code:
• Document your code as you write it (or even before you write it, to help with planning).
• Use plain English and keep the comments concise.
• Avoid redundant comments. For example, the following are not useful:
• x = y + 1 # x is y plus one (bad comment)
• temperature = 30 # the temperature is set to 30 (bad comment)
• You do not need to comment on every single line of your script. Provide comments on
different blocks of the scripts, such as:
• When defining variables
• When calculating using equations
• Explain the loops in the code
• The parameters/results/variables you are plotting
• Include some general information at the first line of the code:
• Your name and student ID number
• The date you created or last modified the script
• A short description of what the script does
Figure 3.4 provides an improved example of the code shown in Figure 3.3. Examine Figure 3.4
and ask yourself how much of the code you can now understand, and what features help your
understanding.
Complete Practice Question 11 from the W4 – Good Programming Practices – Course Notes
Practice Questions
16
ENG1014 Engineering Numerical Analysis Course Notes
the code should print an error message whenever the checking process reveals that the function
has been run incorrectly. If it is not possible to check, then the limitations should be clearly
noted in the function’s comments.
17
ENG1014 Engineering Numerical Analysis Course Notes
Example 3: For a function with an array containing 4 numerical inputs in a vector array:
• “Normal” inputs: np.array([1,0,1,1]), np.array([0,0,0,0]), np.array([-
1,0,1,21])
• Erroneous inputs:"abcd", 1, np.array([1,0,1]), np.array([1,0,1,1,1]),
[1,0,1,1,1], ["a","b","c","d"]
• Boundary cases: [1,0,1,1], np.array([[1],[0],[1],[1]]),
np.array(([1,0],[0,1]))
Reasoning: ndarrays with 4 inputs in a 1D list should always work here! Strings, standalone
numbers, and arrays that have more or less than 4 elements should be rejected. Lists containing
4 numbers can be made to work (or can be deliberately rejected), but lists containing anything
other than numbers are invalid inputs. If the column array is valid, should the 2D matrix
containing 4 elements also be valid? Output testing helps make sure that none of these edge
cases reach a user without being thoroughly documented!
Example 4: For a function with a string input:
• “Normal” inputs: "testing", "TESTING", "TESTing", "123", "abc ", " abc",
"testing input", "a", ""
• Erroneous inputs: ["testing","input2","input3"], 1
Reasoning: strings should always work in this function! Spaces can often cause issues in string
operations, especially trailing white spaces ("abc ", " abc"). Make sure the string is outputting
the case you’re expecting (uppercase, lowercase, or unchanged). Lists and numbers may sneak
through, so always check these. They usually should be failing!
Always test the obvious (normal) cases, as these need to work! Common variable types
(integers, floats, strings, lists, ndarrays) often will find their way into functions. Preparing and
testing for these ahead of time will often allow for quick recognition of these variable types
mistakenly being passed into your user-defined functions.
Complete Practice Questions 12-13 from the W4 – Good Programming Practices – Course
Notes Practice Questions
18