4. Functions#

4.1. Function calls#

In the context of programming, a function is a named sequence of statements that performs a computation. When you define a function, you specify the name and the sequence of statements. Later, you can “call” the function by name.

We have already seen one example of a function call :

type(32)
int

The name of the function is type. The expression in parentheses is called the argument of the function. The result, for this function, is the type of the argument.

It is common to say that a function “takes” an argument and “returns” a result. The result is called the return value.

4.2. Conversion functions#

Python has functions which help you convert between the different python types.

4.2.1. Example 1#

The following code will not work.

# this will not work ...
x = 3.14
y = "5.6"
print(x*y)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_87467/688267819.py in <module>
      2 x = 3.14
      3 y = "5.6"
----> 4 print(x*y)

TypeError: can't multiply sequence by non-int of type 'float'

But there is “fix”.

# ... but it can be fixed
y = float(y)
print(x*y)
17.584

4.2.2. Example 2#

z = "pi = " + str(3.14159)
print(z)
pi = 3.14159

4.2.3. Example 3#

z1 = complex(1,2)
print(z1)
print(z1*z1)
(1+2j)
(-3+4j)

As example 3 demonstrates, the conversion functions also provide a way of “initialising” some of the more complex python types.

4.3. Importing useful functions#

A module in python is a collection of functions, classes, and data that are organised in a group of files specific way. Using this specific way to organise the files allows them to be loaded into another python program using the import directive. There are several ways that this can be done, each with its own advantages. The following examples are perhaps the simplest (but not particularly efficient) way of accessing the contents of a python library.

import math

x = 2
y = math.sin(x)
print(y)
0.9092974268256817
import random

random.seed(0)
x = [1,2,3,4]
random.shuffle(x)
print(x)
[3, 1, 2, 4]

Notice how a module is imported using import

4.4. Point of Departure#

  • There are several ways in which import can be used. Do some online research and find some examples and try and use them.

  • Use the help function to find out what is in the math module. The help you use the help function try executing

help("help")

4.5. Defining new functions#

A function definition specifies the name of a new function and the sequence of statements that execute when the function is called.

def f(x) :
    y = 1/(1+x)
    return y

def is a keyword that indicates that this is a function definition. The name of the function is f. The rules for function names are the same as for variable names: letters, numbers and some punctuation marks are legal, but the first character can’t be a number. You can’t use a keyword as the name of a function, and you should avoid having a variable and a function with the same name.

The x in the parenthesis is an argument that is passed to the function. A function can have a comma separated list of arguments.

def g(x,n) :
    y = 1/(1+x)**n
    return y

The body of the function is indented with a tab, (or 4 spaces), to show it is part of the function. The return statement contains an expression which is what the function evaluates to when it is used.

g(2.1,5)
0.003492943259127733

4.5.1. Exercise 1#

What is the type of g ?

4.5.2. Solution 1#

Hide code cell source

type(g)

Hide code cell output

function

The arguments of a function are variables that name each argument. These can be used explicity when using a function.

def f(a,b,c) :
    return(a+2*b+3*c)

4.5.3. Exercise 2#

Predict the output of the follwing code.

f(1,2,3)

Hide code cell output

14
f(a=1,b=2,c=3)

Hide code cell output

14
f(a=1,c=3,b=2)

Hide code cell output

14
f(1,2,c=3)

Hide code cell output

14
f(1,c=3,2)

Hide code cell output

  File "<ipython-input-48-6cd2a3b71e21>", line 1
    f(1,c=3,2)
             ^
SyntaxError: positional argument follows keyword argument

4.6. Functions and scope#

Consider the following python code

def f(x,y,z) :
    a = 1
    b = 2
    c = 3
    return(a*x+b*y+c*z)
f(1,2,3)
print(a)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-50-aeb7dbfeb3ff> in <module>
      1 f(1,2,3)
----> 2 print(a)

NameError: name 'a' is not defined

The function appears to work ok, but the print statement does not. This is because variables declared in a function are local to that function. This is typically stated as the variable a is contained in the scope of the function f. However, the following will work :

a = 5
def f(x,y,z) :
    a = 1
    b = 2
    c = 3
    return(a*x+b*y+c*z)
f(1,2,3)
print(a)
5

4.6.1. Exercise 3#

What would you expect the output of the following code to be ?

val = False

def exercise3():
    val = True
    
print(val)
exercise3()
print(val)

Hide code cell output

False
False

The exercise3 function creates a new local variable named val and assigns it the value TRUE. This local variable goes away when the function ends, and has no effect on the global variable.

To reassign a global variable inside a function you have to “declare” the global variable before you use it:

def exercise3():
    global val
    val = True

val = False
print(val)
exercise3()
print(val)
False
True

Yukk !! …. Discuss.

4.6.2. Exercise 4#

The following code shows one way of obtaining the real and imaginary parts of a complex number.

z = 2 + 3j
print(z.real)
print(z.imag)
2.0
3.0

Write a function to determine the conjugate of a complex number.

Hide code cell source

def conjugate(z) :
    return(z.real - z.imag*(0+1j))
print(conjugate(z))

Hide code cell output

(2-3j)

4.7. Higher order programming#

In Python, as in some other programming languages, functions have a specific type and can be used like ordinary variables. For example, the following is valid, (and sensible), python (try it).

def f(x,y,z) :
    return(x+2*y+3*z)

g = f
print(g(1,2,3))
print(type(f))
print(type(g))
print(g==f)
14
<class 'function'>
<class 'function'>
True

Given the above definition of f, the following is also valid.

def h(x,func) :
    return(3*func(2*x,2,3))

4.7.1. Exercise 5#

What is the output of the following code ?

print(h(3,f))

Hide code cell output

57

So, functions have type, can be assigned to variables, and passed to functions.

4.8. Closures#

In Python, (and most other languages that support higher order programming), functions can be defined inside other functions. Such a function is called a closure.

def f() :
    def g(x) :
        return(2*x)
    return(g)

h = f()
h(3)

Hide code cell output

6

The following example makes clearer what the value of closures might be. Try it out and check it works.

import math

def logistic_function(k,x0,L) :
    def logistic(x) :
        res = math.exp(-k*(x-x0))
        res += 1
        res = L/res
        return(res)
    return(logistic)

f = logistic_function(1,0,1)
print(f(3))

Hide code cell output

0.9525741268224334

4.8.1. Exercise 7#

What do you think the utility of the code in the previous exercise might be ?

4.8.2. Exercise 8#

Consider the following code.

def agent() :
    sum = 0
    def agent_object(x) :
        nonlocal sum
        sum += x
        return sum
    return agent_object
Now predict what the following code will do.
agent_A = agent()
agent_B = agent()

print(agent_A(3))
print(agent_A(-1))
print(agent_B(5))

Hide code cell output

3
2
5

What happens if you remove the nonlocal keyword from the agent_object function ? Do you think this is good or bad ?

4.9. Recursion#

Functions can “call” themselves. Here is a simple example.

def rsum(l,sum = 0) :
    if l == [] :
        return sum
    return rsum(l[1:],sum + l[0])


rsum([1,2,3,4])
    
10

4.9.1. Exercise 9#

  • What limitations, if any, does rsum have ?

  • Are there any efficiency issues ? Is so, how might you mitigate them ?

  • Rewrite the rsum function in a way that does not use recursion

4.10. Functions and iteration#

Functions can be used to create iterators. For example, here is a function used to create an iterator that produces a Fibonacci sequence. Not the use of the yield directive.

def ifib(a,b,n) :
    for i in range(n) :
        c = a + b
        a = b
        b = c
        yield c
    return
for i in ifib(1,1,10) :
    print(i)
        
2
3
5
8
13
21
34
55
89
144
it = ifib(1,1,10)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
2
3
5
8

Recursive functions can also be used to construct iterators using yield from.

4.10.1. Exercise 10#

  • Have a look at this article relating to the use of yield from in recursice functions.

  • Re-implement the ifib function using recursion.