Be sure to do all exercises and run all completed code cells.
If anything goes wrong, restart the kernel (in the menubar, select Kernel\(\rightarrow\)Restart).
Writing Functions¶
Remember to type in and run all the examples as you go through these materials.
Study each example and make sure you understand what is happening before moving on to the next
Functions¶
Another crucial part of programming is the
ability to define functions when we want to do the same thing in several places.
A function is a rule that takes input values and produces output values.
Python functions are not quite the same as
mathematical functions.
However in Python a
function that is called several times with the same input can return
different values each time. A Python function can also have side
effects, such as output to the screen or to a file.
We will start of by exploring some of the functions that are available in Python and then we will see how to build our own functions.
Built-in Functions¶
We have already come across several functions that are available
directly in Python: print
, int
, float
, round
, list
, range
…
Some others are type
, abs
, min
and max
.
To use a function you need the name of the function followed by the arguments in brackets.
The arguments can be numbers, variables, calculations or other functions.
The functions we had already seen and also the abs
and type
function take one input, known as its “argument” and return one thing back, known as the result.
int(4.436e2), round(4.436e2), float(-68), type(42), type("Hello")
abs(5), abs(-5), abs(6.01 + 0.01), abs(-6.01 - 0.01)
The min
and max
functions take two (or more) arguments and return one result.
If there are several arguments they have to be separated by commas.
max(-3, 5)
min(-3, 5, 0, -10)
Returned values¶
Some functions like sqrt()
return a value that can be used, others just perform a function but do not give anything back, such as the print()
function, which only prints to the screen but does not return a value:
from math import sqrt
a = sqrt(2)
print(a)
print(42)
b = print(42)
print(b)
A special value of
None
was returned to the variableb
.
Another very useful built-in function is the help function.
The help
function takes a single argument and
prints useful information about the
thing passed on as the input argument.
We can use this get information about
what a function does and how to use it.
help(abs)
Imported Functions¶
In the previous section we saw that to access certain objects (constants or functions), we have to load a library first.
import math
print(math.exp(1))
from math import sqrt, sin, pi
print(sqrt(36))
print(sin(pi/2))
x = 2
print(
math.log(math.exp(x)),
math.exp(math.log(x))
)
The mathematical functions in the math
module always work with
floating point numbers. This means that integers will be converted to
floating point numbers first. The expression math.sqrt(36)
will return
the floating point number 6.0
even though the result could be
represented by an integer.
The help
function can also be applied to modules. This will result in
a list of all the functions and constants contained in that module,
together with their documentation. This information can also be found in
the Python documentation, but it is often convenient to have it
available directly.
Try this for the
math
module by printinghelp(math)
.
The built-in function dir
allows us to get just a list of names of
functions and constants for a module.
import math
print(dir(math))
Defining Functions¶
We can define our own functions using the def
keyword.
def sq(x):
return x**2
A function definition starts with the keyword
def
,then comes the name of the function and a set of brackets,
The names of functions and parameters have to follow the same rules as the names of variables
(letters, numbers and underscores, no numbers or underscores at the start).
inside the brackets put any parameters that are to be used in the function
multiple parameters are separated by commas.
a colon must follow the brackets
After the colon, starting on the next line, we can have one or more lines of code that make up the function.
anything the function does must be indented by the same number of spaces (conventionally 4)
the contents of the function are known as a “code block” (see below).
Anything indented will be run together when the function is used, but nothing afterwards.
Once a function is defined, it can be called in exactly the same way as
a built-in function.
The return
line says what the output of the function will be.
The returned output value can then be used as a variable or printed.
def sq(x):
return x**2
print(sq(2))
u = 3.5
v = sq(u+0.5) - 1.0
print(v)
Exercise:¶
Define the function \(f(x) = \dfrac{x^2}{3 + x}\).
Determine the quantities \(f(0)\), \(f(-1)\) and \(f(1/3)\).
The structure of the code will be like this:
#define function name and arguments here
def SOME_FUNCTION_NAME( INPUT_ARGUMENT_VARIABLE_NAME_TO_USE_IN_FUNCTION )
#indented code for what to return to outside programme
return OUTPUT_VALUE_TO_RETURN_TO_MAIN_PROGRAMME
# YOUR CODE HERE
print(f(0))
# perform other tests:
# YOUR CODE HERE
Result:
0.0
0.5 0.03333333333333333
Indentation of Code Blocks¶
The code run by the function must be indented by the same number of spaces.
This tells Python what is part of the function and what is outside it.
We can run many commands inside a function, including printing to the screen, rather than returning a value to be printed or used in a variable.
def FUNCTION_NAME(ARGUMENTS):
#<-four spaces indented line,
# any code run by the function
# when it is used
# needs to be in this indented block
# anything outside the function
# goes back to normal alignment
return RETURNED_VALUE
#anything after the return action is ignored...
def do_things(a_number):
print("Your number is %.2f to 2DP" % a_number)
print("Squared it is:", a_number**2)
asqrt = a_number**0.5
print("The square root of " + str(a_number) + " has been returned")
return asqrt
print("This does nothing because a return statement terminates the function!")
print("This is not inside the function!")
x = 5.499025
y = do_things(x)
print("This is after the function finishes!")
Notice: the last
print()
in thedo_things()
function is ignored because a function ends on areturn
statement!
print(y)
Note: When a function is used any arguments needed must be given in the brackets.
Also the name of the argument in the function definition a_number
and that given as an argument when applying the function x
are unrelated.
Exercise: Truncate Decimal Places¶
Write a function trunc5dp
that truncates a number after the fifth decimal place. For example, trunc5dp(4321.1234567) = 4321.12345
.
The steps (algorithm) for doing this are:
Multiply by \(10^5\) (either written
100000
or1e5
), - e.g. \(4321.1234567\) becomes \(432112345.67\)use the
int()
function to remove trailing decimals, - e.g. \(432112345.67\) becomes \(432112345\)divide by \(10^5\). - e.g. \(432112345\) becomes \(4321.12345\)
the structure of the function is like this:
#function name definition, with 1 argument
# indented code block
# using algorithm
# steps goes here
# return the answer
# YOUR CODE HERE
ans = trunc5dp(4321.1234567)
print(ans)
Expected Result: 4321.12345
Does your function do what you expect for negative numbers?
print(trunc5dp(-4321.1234567)) # truncates (round towards zero)
Expected Result: -4321.12345
Naming arguments¶
The names that you give parameters in the definition of the function are only used inside the function.
They do not relate to the arguments and variable names in the main program.
def myfunc(temporary_variable_name):
answer = temporary_variable_name * 2
return answer
x = 10
# x will be renamed temporary_variable_name inside the function (and only there)
y = myfunc(x)
print(y)
print(temporary_variable_name) # this will lead to an error
print(answer) #so will this
Think about this…
Variable names used inside functions do not affect variables used in the rest of the program:
a = 1
def root(a):
a = a**0.5 #this a is only a tempory name
return a
b = 2
print(root(b))
print(a)
Here
a
is a different thing inside to outside the function. Make sure you are clear on this.
Avoiding confusion with parameters that are not arguments¶
Be careful using parameters that are not passed into the function as arguments, as this can cause confusion.
The following will cause an error when trying to apply (“call”) the function not at the time of defining it!
def funky(ar):
ans = ar+bee
return ans
print("Here!")
funky(20)
Understanding the error message:¶
Note the first part of the error message that points to the origin of the problem on line 7:
----> 7 funky(20)
which then traces the issue back to the function definition on line 2:
<ipython-input-1-40d916a0d279> in funky(ar)
1 def funky(ar):
----> 2 ans = ar+bee
3 return ans
before finally saying what was not understood by the Python interpreter:
NameError: name 'bee' is not defined
Avoiding the error¶
Either pass all parameters into the function as arguments, or make sure they are definitely defined before the function is called:
def funky(ar):
ans = ar+bee
return ans
bee = 8222
funky(778)
Exercise: Calculating Area¶
Complete the following program by writing a functions to calculate the area of a circle, given its radius (\(a = \pi r ^2\));
## Instructions: your code should have this structure:
# def circle_area(r):
# return ??
from math import pi
# YOUR CODE HERE
print(circle_area(2.0))
Expected Result: 12.566370614359172
Example: No Parameters¶
def the_answer():
return 42
ans = the_answer()
print(ans)
Example: Multiple Parameters¶
def quadratic_value(a, b, c, x):
"Evaluate a quadratic polynomial a*x**2 + b*x + c"
return a*x**2 + b*x + c
help(quadratic_value)
print(quadratic_value(2.0, 3.0, 4.0, 0.5))
If a function requires several parameters, we need to separate the parameter names by commas.
The example also shows the use of a documentation string, which can be useful for the user.
The
help
function will print the documentation string, as well as the name of the function and the function parameters.
Local Variables¶
Any variables that are assigned inside a function definition are called local
variables, and only exist inside the function.
This means that any local variables that we use for intermediate calculations inside a function will not interfere
with the variables in the rest of the program.
Study and understand what is going on in the following example code blocks:¶
import math
def ellipse(a, b):
ab = a*b
print(ab)
return math.pi*ab
ab = 4 # unrelated to local variable ab in ellipse
print(ab)
a = 2.0
b = 3.0
A = ellipse(a, b)
Note:
ellipse()
printed the internal value ofab
to the output, but returned te value of \(\pi a b\) to the variableA
:
print(A)
#the value of ab defined outside the function remains unchanged!
print(ab)
Multiple Return Values¶
A function can return several values, by separating the values by commas in the return statement.
The following example shows a function that returns the two roots of the quadratic equation \(a x^2 + b x + c = 0\).
import math
def quadratic(a, b, c):
"return the roots of the quadratic equation a*x**2 + b*x + c = 0."
discriminant = b**2 - 4*a*c # assumed to be positive
offset = math.sqrt(discriminant)
x1 = (-b + offset)/(2*a)
x2 = (-b - offset)/(2*a)
return x1, x2
print(quadratic(4.0, 0.0, -16.0))
We can assign names to each of the return values using the following syntax:
root1, root2 = quadratic(4.0, 0.0, -16.0)
print(root1)
print(root2)
Note that the names of the local variables (
x1
andx2
) do not have to be the same as the variables we use to name the results of the function (root1
,root2
).
Exercise: Calculating perimeter and area of a square¶
Define a function to calculate the perimeter and area of a square, given the side length (\(P = 4L\), \(A=L^2\));
# YOUR CODE HERE
print(square_geometry(3))
Expected Result: (12, 9)
Exercise: Calculating Perimeter and Area of a rectangle¶
Write a function to return the perimeter and area of a rectangle, given the width and height (\(P=2W + 2H\), \(A=W\times H\)).
test your function with \(W=3\) and \(H=4\)
# YOUR CODE HERE
#set W and H here
perimeter, area = rectangle_geometry(width, height)
print(perimeter)
print(area)
Expected Result:
14
12
# Run this block to test your function on a hidden test case!
# this will only work running on ace.jupyterhub.bath.ac.uk in the AR10366 folder
import sys
sys.path.append('.checks')
import check03a
check03a.test0302(rectangle_geometry)
No Return Values¶
It is possible to define functions that do not return any values at all. These are used to perform tasks such as printing, reading or writing files, accessing the internet, or plotting graphs.
def hello():
print("Hello World!")
hello()
The function does not contain a return
statement and
therefore the function does not return a value, just a special value
None
.
greeting = hello() # the action of printing will still be performed
print(greeting)
Optional Arguments and Keyword Arguments¶
Sometimes arguments can be given default values using an equals sign.
If that argument is not entered when applying the function it takes the default value.
def raisepower(base, exponent=2):
return base**exponent
print(raisepower(10))
print(raisepower(10, 5))
print(raisepower(2, exponent=10))
Keyword arguments must appear after compulsory arguments that do not have default values.
If there are more than 3 or 4 arguments it is a good idea to have them as optional keyword arguments with default values.
Functions names as arguments¶
Functions can also be used as input parameters (arguments) in Python.
from math import cos, sin, tan
def f_zero_ten(InputFunction):
print(InputFunction(0.0), InputFunction(10.0))
f_zero_ten(cos)
f_zero_ten(sin)
f_zero_ten(tan)
Note: the function name is passed without parentheses ( )
if you input a function with arguments e.g.
cos(0)
then only the numerical value returned by \(\cos(0)=1\) is seen by the function.
from math import cos
def f_zero(fname):
return fname(0.0)
x=0
f_zero(cos(x))
Understanding the error:¶
The error is first captured on line 8 when we pass cos(x)
as an argument to our f_zero
function:
----> 8 f_zero(cos(x))
But the error really occurs when trying to call (apply) fname
as a function:
----> 4 return fname(0.0)
The error message states:
TypeError: 'float' object is not callable
This is because we passed
cos(x)
withx=0
into our function, but \(\cos(0)\) is just the number 1 (afloat
:1.0
).the floating point number
1.0
is not a function so cannot be used (“called”) as if it were a function:1.0(0.0)
does not mean anything.
We would see a similar error using (calling) any other non-function as a function:
import math
answer = "The answer is:" #the variable answer holds a string
answer(42) #trying to pass an argument to the variable attempts to use the above string as a function
Exercise:¶
Define a function named
twice()
that takes a functionfun
and a valuexval
as two arguments, then returns \(f(f(x))\)
? ?(?, ?):
return ?(?(?))
# define your twice() function below:
# YOUR CODE HERE
#function to give to your twice() function
def two_times_x(x):
return 2*x
# give the function two_times_x as an argument to the twice() function along with the value 10
#a2 = twice(???, ???)
# YOUR CODE HERE
print(a2) #this should be the value of two_times_x(two_times_x(10))
Expected Result: 40
# Run this block to test your function...
# this will only work running on ace.jupyterhub.bath.ac.uk
import sys
sys.path.append('.checks')
import check03
# Test 1. test function to give to your twice() function
def x_squared(x):
return x**2
# Test 2.
from math import sqrt
# the sqrt function will also be used to evaluate sqrt(sqrt(16))
check03.test0303(twice)
Summary¶
Defining a function¶
def rootmeansquare(a, b):
c = a**2 + b**2
print(f"The sum of squares is {c}")
m = c/2
print(f"The mean of squares is {m}")
return m**0.5
Calling a function¶
x = 6
y = 9
s = rootmeansquare(x, 2*y) # calling a function
print(f"RMS = {s:.4}")
Output:¶
The sum of squares is 360
The mean of squares is 180.0
RMS = 13.42
Functions with more than one output (returned value)¶
def minmax(a,b,c):
minval = min(a,b,c)
maxval = max(a,b,c)
return minval, maxval
mn,mx = minmax(69,42,404)
print("Smallest is:", mn)
print("Largest is:", mx)
Output:¶
Smallest is: 42
Largest is: 404
Functions with optional arguments¶
def sigfig4(value_to_use, print_string="Rounded value:"):
"Function to round to 4 significant figures"
print(print_string, f"{value_to_use:.4}")
#Note: does not return a value!
r2 = 200**0.5 #square root 200
sigfig4(r2, print_string="sqrt(200) is:")
returned = sigfig4(2**0.5)
print(returned)
Output:¶
sqrt(200) is: 14.14
Rounded value: 1.414
None
Functions with function arguments¶
def func_difference(dummy_function_name, value1, value2):
y1 = dummy_function_name(value1)
y2 = dummy_function_name(value2)
diff = y2-y1
return diff
from math import sqrt
ans = func_difference(sqrt, 4, 9)
# inside func_difference the calculation is sqrt(9)-sqrt(4)
print(ans)
Output:¶
1.0
Pitfalls¶
Watch out for the following sources of errors:
Missing colon before indentation block.
Wrong or inconsistent indentation. Use for 4 spaces (not tabs) per indentation level.
Task 3 (2%):¶
Numerical Differentiation¶
Background¶
Differentiation of a function \(f(x)\) about any point \(x=a\) can be defined as follows: $\(f'(a) = \lim\limits_{\delta x \rightarrow 0} \frac{f(a + \delta x) - f(a)}{\delta x}\)$
Therefore a numerical approximation to the gradient at a point \(a\) can be obtained by evaluating f(a)
and f(a+dx)
after
some small increment \(\delta x\) (which we name as dx
for simplicity)
The code below uses this technique to differentiate the specific function for the area of a cube:
\(A=6 L^2,\)
with side length \(L=2\)
def area_cube(L):
area = 6.0 * L**2.0
return area
a = 2 #value of x=a to differentiate at
dx = 0.1 #delta x
a1 = area_cube(a)
a2 = area_cube(a+dx)
f_x = (a2 - a1)/dx #differentiation formula
print(f_x)
Look back at
"floating point precision"
in week 1 to explain the last digit.
PART 1 (warm-up exercise, do not submit this)¶
Copy the code above and change change the value of \(\delta x\) to 0.01, 0.001 and 0.0001 to see how it affects the numerical approximation to the true value of 24.
def area_cube(L):
area = 6.0 * L**2.0
return area
x = 2
# YOUR CODE HERE
a1 = area_cube(x)
a2 = area_cube(x+dx)
f_x = (a2 - a1)/dx #differentiation formula
print(f_x)
PART 2 (for submission):¶
Write a function that can take any function as an argument and differentiate it¶
Step 1¶
Using the notes to guide you:
define a new function called
diff()
with three arguments:func
,a
anddx
.the
diff()
function should evaluater1 = func(a)
andr2 = func(a+dx)
and use these values in the formula for the gradient given in the Background and call itgrad
(or whatever you like).The
diff()
function shouldreturn
the value ofgrad
Step 2¶
Test your diff()
function by giving it the test_function(x)
that returns the result of \(g(x) = x^3 - 2x^2 - 5\)
Give the test function name as the first argument to your
diff()
function to differentiate it atx=10
anddx=1e-3
(or0.001
).
# here define a function called diff(internal_function_name, OTHER, ARGUMENTS):
# YOUR CODE HERE
# leave this function as it is here
def test_function(x):
return x**3 - 2*x**2 - 5
a=10
dx=1e-3
#answer = diff(???)# apply your diff function here with arguments of the test function name, the value of x and the step-size.
# YOUR CODE HERE
print(answer)
Expected Result: 260.02800099990964
precision may vary very slightly on different computers, don’t worry about this if it’s OK below.
restart the kernel and check again!
Use the tests below to check it will pass the autograder
Once it’s working, put the code from the cell above into a new python script, check in Spyder (etc.) it works as expected from scratch and submit.¶
Use the test cell below to check that your function will pass the grading script:¶
Only works on ACE Jupyterhub in your
AR10366
folder
# this will only work running on ace.jupyterhub.bath.ac.uk in AR10366 folder
import sys
sys.path.append('.checks')
import check03
#Test 1. Try the test function
def test_function(x):
return x**3 - 2*x**2 - 5
# at the values:
a=10
dx=0.001
#Test 2. Differentiating another (secret) function at another value
check03.testASS03(diff)