Functions

Introduction to Software Engineering (CSSE 1001)

Author

Paul Vrbik

Published

March 13, 2025

An (imperative) programming language must provide three constructs:

  1. Sequencing (instructions have order).
  2. Selection (instruction can be skipped).
  3. Iteration (instructions can be repeated).

Sequence (Top-to-bottom evaluation.)

Code order is relevant. Executing the same commands in a different order will (probably) change the output.

Example 1 Here is a simple sequence of code.

x = 1 
x = x + 1 
x = 2*x 
x
4

Let us now swap two lines and observe there is a difference in output.

x = 1 
x = 2*x     # swapped lines
x = x + 1   #
x
3

Example 2 An example with multiple variables.

x = 1
y = 2
x = y  
y = x  
x, y
(2, 2)
x = 1
y = 2  
y = x  # swapped lines
x = y  #
x, y
(1, 1)

Functions

Functions perhaps are the single most important feature of programming languages. Functions enable us to abstract away complexity by bundling lines of code together. Functions are the building blocks of more sophisticated functions.

We have already used functions: * and max for example. Each of these takes input and return output:

  1. Multiplication takes two numbers and returns a number.
  2. max takes two or more numbers and returns a number.

Functions are analogous to that of mathematics and can also be named.

For instance, a parabola in mathematics is given by the function
\[f(x) = x^2 + x + 1\] and in Python we write

def f(x):
    return x**2 + x + 1

which loads f into memory for invocation like

f(3)
13

In expressions like

f(2) + f(3)
20

we can regard f(1) as being replaced by its return value.

7 + 13
20

Notice that if we do not invoke (by using brackets) the function Python will not return an error

f
<function __main__.f(x)>

because the function is itself an object with

type(f)
function

living in memory at location

id(f)
140598370846176

The Python visualizer illustrates the loading of f and subsequent invocation in the following way.

Function Invocation

Here is another example with two inputs.

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

Which can be called in a more sophisticated way to demonstrate how multiple frames will be created (and deleted) to resolve the outtermost expression.

f( -f(5, 2) + 12, f(2, 3) )
12

No Input

A function need not take input — though brackets must still be used to invoke it.

def foo():
    return 905
foo
<function __main__.foo()>
foo()
905

Spaces Matter

In Python, four spaces/indents are significant and are used to associate lines of code with control structures (in this case function definition).

def f(x):
␣␣␣␣return x**2 + x + 1

If you get errors along the lines of:

IndentationError: unexpected indent

check your formatting.

Return vs Print

return is a reserved word (a name that cannot be assigned by us). It is used to designate what value the function should return while also terminating the function itself. The return statement causes a function to exit and hand back a value to its caller.

print is a function that returns nothing whose side effect is that it displays a string.

x = print("Hello World")
type(x)
Hello World
NoneType

This distinction is important because return and print can look like they are doing the same thing.

Consider the following REPL interaction.

def f(x, y):
    print(x+y)
x = f(2, 3)
5
y = f(3, 4)
7

This seemingly looks like it is working but the astute reader will notice 5 printed whereas we normally expect no output from an assignment imperative. More problematic is that x and y currently store None, the default return value so we cannot add x and y which we thought had numbers in them.

x + y
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

If a function does not have a return statement it is presumed return None is the last line.

def f(x, y):
    print(x+y)
    # return None implicitly

Compare this with the use of return.

def f(x, y):
    return x + y

x = f(2, 3)  
y = f(3, 4)  

x + y
12

Notice return is not a function but a reserved word.

That is, we do

def f(x, y):
    return x + y

and not

def f(x, y):
    return(x + y)

Examples

Notice the difference in output in the following functions. Note that each differs only by where the return statement was placed.

def example(x):
    print(1*x)
    print(2*x)
    return 3*x 

a = example(1)  # prints 1, 2
a
1
2
3
def example(x):
    print(1*x)
    return 3*x
    print(2*x)  # This line won't execute

a = example(1)  # prints 1
a
1
3
def example(x):
    return 3*x
    return 2*x
    print(1*x)  # This line won't execute

a = example(1)  
a
3
def example(x):
    print(2*x)

a = example(1)  # prints 2
a  # is None
2
print(type(a))  # <class 'NoneType'>
<class 'NoneType'>

Case Study: Heron’s Formula

The area of \(\triangle ABC\) given by

Figure 1: Setup for Heron’s formula.

is \[\sqrt{s(s-a)(s-b)(s-c)}\] where \(s = \frac{1}{2}(a+b+c)\).

Let us verify Heron’s formula with an easy example. The “3 4 5” triangle (a right-angle triangle with side lengths 3, 4, and 5) has area half its base time height \(\frac 1 2 \cdot 3 \cdot 4 = 6\).

(0.5*(3+4+5)
*(0.5*(3+4+5)-3)
*(0.5*(3+4+5)-4)
*(0.5*(3+4+5)-5))**0.5 
6.0

But suppose we want to verify more, we have to type a lot and tt would be hard to repeat.

If we utilize sequencing we can simplify a bit.

a, b, c = 3, 4, 5
s = (a + b + c)/2
s*(s-a)*(s-b)*(s-c)**0.5
36.0

Now we just have to change the values of a, b, and c and the result will be printed by the repl. However we would have to copy this whenever and new calculation was required, and if we made a mistake we would have to make a correction in many places.

Better yet we want to bundle the sequence into a function. This streamlines obtaining the area and also isolates problems to a single place in the code.

def triangle_area(a, b, c):
    s = (a + b + c)/2
    return (s*(s-a)*(s-b)*(s-c))**0.5

triangle_area(3, 4, 5)
6.0

Type Annotations

In mathematics, we would define a mapping: \[ \mbox{triangle-area} : \mathbb{R} \times \mathbb{R} \times \mathbb{R} \to \mathbb{R} . \] to specify what the domain and range of our function is.

In Python we can specify the expected type of each input variable and the type of the return value…

def triangle_area(a: float, b: float, c: float) -> float:
    s = (a+b+c)/2
    return (s*(s-a)*(s-b)*(s-c))**0.5 

triangle_area(2.2, 3.3, 4.4)
3.5147323866832307

This is called a type annotation.

Important

Type hints are not enforced but are rather documentation for making the code more readable.

Let us change the all the types in triangular_area to str and see what happens when we invoke it with the “wrong” input.

def triangle_area(a: str, b: str, c: str) -> str:
    s = (a+b+c)/2
    return (s*(s-a)*(s-b)*(s-c))**0.5 

triangle_area(2.2, 3.3, 4.4)
3.5147323866832307

We get the correct answer (for floats) and no error.

Improving our function (Doc Strings/Examples)

def triangle_area(a: float, b: float, c: float) -> float:
    """
    Return the area of the triangle with sides length a, b, and c.

    Preconditions: 
        a, b, c are all nonzero positive floats.

    >>> triangle_area(3, 4, 5)
    6.0
    """
    s = (a+b+c)/2
    return (s*(s-a)*(s-b)*(s-c))**0.5

The general framework follows.

def function_name(arg0: type, arg1: type, ... ) -> type:
    """
    Short description of function for documentation.

    Preconditions (if any).

    >>> function_name(x, y, ...)
    expected output
    """
    # function body
    return 

Python Enhancement Proposal

PEP stands for Python Enhancement Proposal. It is a design document providing information on how to style our code.

In particular, follow Google’s PEP which is a variant of PEP8.

We discuss the relevant part of the PEP now.

Pep 8 – Style Guide for Python Code

Variable and Function names

Names must start with a letter and not include special characters excepting underscore “_’’. Function and variable names should be lowercase, with words separated by underscores as necessary to improve readability.

Yes No
descriptive_variable_name DescriptiveVariableName

Breaking Long Computation

According to PEP8 all lines must be strictly less than 80 characters wide.

Redundant bracket that encloses an expression enable us to break expressions across multiple lines.

income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

We can also use the line continuation character \

income = gross_wages \
          + taxable_interest \
          + (dividends - qualified_dividends) \
          - ira_deduction \
          - student_loan_interest

But otherwise we would generate an error.

income = gross_wages 
          + taxable_interest 
          + (dividends - qualified_dividends) 
          - ira_deduction 
          - student_loan_interest
ERROR FILL THIS IN

Comments

Anything following a # will be ignored by Python. In addition to docstrings, you can (and should) include comments in your code to explain something not obvious in your design.

Strive to include useful comments. For example, the following is not helpful.

x = x + 1                 # Increment x

It is explaining something evident from the code itself. The pertinent information is why x is being incremented (if it is not otherwise obvious).

This has a better comment because it gives context to the code.

x = x + 1                 # Compensate for border

Spacing

These are not hard rules but guidelines. Generally speaking you want to optimize for readability by grouping things with higher operational precedence.

Yes No
i = i + 1 i=i+1
x = x*2 - 1 x = x * 2 - 1
hypot2 = x*x + y*y hypot2 = x * x + y * y
c = (a+b) * (a-b) c = (a + b) * (a - b)

Exercises

Exercise 1 Write a function that takes three integers and returns the number which is not the largest or the smallest. Do not use any Python features (like if-statements) that we have not yet introduced.

def middle_number(x: int, y: int, z: int) -> int:
    """Return the number that is not the largest or smallest 
    among the three inputs.
    
    Precondition: The numbers are distinct.
    
    >>> middle_number(3, 1, 2)
    2
    >>> middle_number(2, 3, 1)
    2
    """
    return (x+y+z) - min(x, y, z) - max(x, y, z)

Exercise 2 Consider the function foo defined below that computes the area of a circle with integer radius. What is the type of its return value?

def foo(radius: int):
    """
    Precondition:  radius > 0
    """
    area = 3.14159 * radius**2

NoneType. There is no explicit return statement.

Exercise 3 Is this valid Python code?

def foo(x: int, y: int) -> int:
    """
    >>> foo(2, 3)
    6
    """
    return x

ans = foo("oi", 3)

Yes. Type-hints are not enforced.

Pep 8

Style Guide

Further Resources