Wednesday, September 9th 2026¶

Last week, we discussed for loops, while loops, and if/elif/else statements. In particular, we used a for loop and an if statement to test whether a given positive integer num was prime.

In [1]:
num = 43

num_is_prime = True
for d in range(2,num):
    if (num % d == 0):
        num_is_prime = False
        break

if num_is_prime:
    print(num, 'is prime.')
else:
    print(num, 'is not prime.')
43 is prime.

We can wrap the code above up inside another for loop to test many numbers for primality.

In [2]:
for num in range(2, 21):
    num_is_prime = True
    for d in range(2,num):
        if (num % d == 0):
            num_is_prime = False
            break
    
    if num_is_prime:
        print(num, 'is prime.')
    else:
        print(num, 'is not prime.')
2 is prime.
3 is prime.
4 is not prime.
5 is prime.
6 is not prime.
7 is prime.
8 is not prime.
9 is not prime.
10 is not prime.
11 is prime.
12 is not prime.
13 is prime.
14 is not prime.
15 is not prime.
16 is not prime.
17 is prime.
18 is not prime.
19 is prime.
20 is not prime.

Notice that the code inside the for loop (which tests num for primality) is nearly identical to the first code cell. It would be nice if we could store this set of operations in someway that we can call upon as needed.

Functions in Python¶

Very often, we want to write code that can be applied to many different inputs. For example, we might want to test many different numbers to check whether they are primes (as in the above). We can accomplish this by writing a Python function. The syntax for defining functions in Python is: def <function name>(<some inputs, separated by commas>): ...do something...

As another reminder: the spacing is part of the code. Lines that are indented are considered part of the function, and once we un-indent back to vertically align with def <function name>(...), we're no longer inside the function.

In [3]:
def f():
    print('Hello world!')
In [4]:
def g(x,y):
    print(x * y)

Notice that when we define a function, the actions included inside are not immediately performed.

Instead, after defining a function we can call upon it to execute the function's code. To call a function, we simply write <function name>(<whatever inputs that are necessary, separated by commas>).

In [5]:
f()
Hello world!
In [6]:
g(3,7)
21
In [7]:
g('Wednesday', 5)
WednesdayWednesdayWednesdayWednesdayWednesday

Exercise: Write a function called is_prime that takes in a variable n and prints whether or not n is prime. Then test your function against the integers from $2$ to $40$.

In [8]:
def is_prime(num):
    num_is_prime = True
    for d in range(2,num):
        if (num % d == 0):
            num_is_prime = False
            break
    
    if num_is_prime:
        print(num, 'is prime.')
    else:
        print(num, 'is not prime.')
In [10]:
is_prime(123)
123 is not prime.
In [11]:
for num in range(2,41):
    is_prime(num)
2 is prime.
3 is prime.
4 is not prime.
5 is prime.
6 is not prime.
7 is prime.
8 is not prime.
9 is not prime.
10 is not prime.
11 is prime.
12 is not prime.
13 is prime.
14 is not prime.
15 is not prime.
16 is not prime.
17 is prime.
18 is not prime.
19 is prime.
20 is not prime.
21 is not prime.
22 is not prime.
23 is prime.
24 is not prime.
25 is not prime.
26 is not prime.
27 is not prime.
28 is not prime.
29 is prime.
30 is not prime.
31 is prime.
32 is not prime.
33 is not prime.
34 is not prime.
35 is not prime.
36 is not prime.
37 is prime.
38 is not prime.
39 is not prime.
40 is not prime.

The return command¶

It is often more useful to have functions like the above return either a True or a False Boolean depending on whether n is prime or not.

More generally, we often want functions to return some object (e.g. a list, a float, an integer, a string, etc) to the caller. This can be done using a return statement. The syntax is: def <function name>(<some inputs, separated by commas>): ...do something... return <some object to be returned>

When calling a function that returns something, we can store the output as a new variable. For example, output = <function name>(<whatever inputs that are necessary>) will store the returned object as the variable output.

In [23]:
def f():
    print('Hello world!')
    return 'Goodbye'

    print('Did this run?')
In [24]:
def g(x,y):
    print(x * y)
    return x + y
In [25]:
out = f()
Hello world!
In [26]:
print(out)
Goodbye
In [27]:
out = g(3,7)
21
In [28]:
print(out)
10

Exercise: Rewrite the is_prime function to return a Boolean True if the input is prime and False if not.

In [29]:
def is_prime(num):
    num_is_prime = True
    for d in range(2,num):
        if (num % d == 0):
            num_is_prime = False
            break
    
    return num_is_prime
In [30]:
is_prime(43)
Out[30]:
True
In [32]:
for num in range(2,41):
    if is_prime(num):
        print(num, 'is prime.')
    else:
        print(num, 'is not prime.')
2 is prime.
3 is prime.
4 is not prime.
5 is prime.
6 is not prime.
7 is prime.
8 is not prime.
9 is not prime.
10 is not prime.
11 is prime.
12 is not prime.
13 is prime.
14 is not prime.
15 is not prime.
16 is not prime.
17 is prime.
18 is not prime.
19 is prime.
20 is not prime.
21 is not prime.
22 is not prime.
23 is prime.
24 is not prime.
25 is not prime.
26 is not prime.
27 is not prime.
28 is not prime.
29 is prime.
30 is not prime.
31 is prime.
32 is not prime.
33 is not prime.
34 is not prime.
35 is not prime.
36 is not prime.
37 is prime.
38 is not prime.
39 is not prime.
40 is not prime.

Note: Whenever a function hits a return statement, it will immediately terminate after returning the corresponding object. That is, none of the code in the function will be run after it hits a return statement.

In [ ]:
 
In [ ]:
 
In [ ]:
 

Exercise: Write a function get_primes that takes in an integer $n$ and returns a list of all prime numbers between $2$ and $n$ (inclusively).

In [36]:
def get_primes(n):
    primes = []
    for num in range(2,n+1):
        if is_prime(num):
            primes.append(num)
    return primes
In [41]:
get_primes(40)
Out[41]:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

Functions and namespaces in Python¶

In Python, there are various levels at which variables can be defined. If we define a variable directly within a cell (or within a loop or if block), then that variable is globally available. We say that it is part of the global namespace. Any other piece of code can call upon this variable as needed.

For example, when writing a function, we can make use of this variable within the function. If a function calls upon a variable that has not been defined within that function, it will look outside to see if it has been defined globally, and if so, use the globally defined value.

In [42]:
a = 7
In [43]:
b
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[43], line 1
----> 1 b

NameError: name 'b' is not defined
In [44]:
def f():
    print(a)
In [45]:
f()
7

On the other hand, variables that are defined within a function only exist locally within that function. Consider the following code snippets that illustrate this point.

In [46]:
def f():
    b = 1
    print(a + b)
In [47]:
f()
8
In [48]:
a + b
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[48], line 1
----> 1 a + b

NameError: name 'b' is not defined
In [49]:
def g():
    new_variable = 6
    print(new_variable)
In [50]:
g()
6
In [51]:
print(new_variable)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[51], line 1
----> 1 print(new_variable)

NameError: name 'new_variable' is not defined

Notice that, while the function g has internally defined new_variable, that definition does not extend outside of the function. When trying to access new_variable from outside of the function, we get an error.

Similarly, if we redefine a globally defined variable within a function, that new definition only holds within the function.

In [59]:
variable = 7

def f():
    variable = 10
    print(variable)
In [60]:
f()
10
In [61]:
print(variable)
7

Notice that, while the function f has internally redefined variable to take on a new value, that has not changed the value of the globally defined variable.

When calling upon functions, they have their own local namespace. That is, they have their own collection of defined variables that is separate from the global namespace. However, as we've seen, these local namespaces can inherit variables from the global namespace if they call upon a name that has not been locally defined.

In [62]:
def f(x,y):
    print(x**2 + y**2)
In [64]:
x = 1
y = 2
a = 3
b = 4

f(a,b)
25
In [65]:
print(x,y)
1 2

Exercise: Write a function that takes in an integer n and returns the first n prime numbers.

In [ ]:
 
In [ ]:
 

In-class exercise:¶

Exercise: Write a function get_prime_factors that takes in a positive integer n and return a list of all prime numbers that divide n.

In [73]:
 
In [ ]:
 
In [ ]: