03. Functions

Computational methods

Author

Marco A. Alsina

Published

May 19, 2026

Learning objectives

  • Understand the purpose and structure of functions in Python
  • Understand the scope of local and global variables in functions
  • Define and call functions with different argument types
  • Apply default, positional, keyword, and flexible arguments
  • Write modular and reusable code using functions

Functions

A function is a named, reusable block of code that performs a specific task. Functions are a cornerstone of modular programming: the practice of breaking a program into smaller, self-contained units that are easier to write, test, and maintain.

Functions in Python are declared with the def keyword, followed by the function name, a list of parameters enclosed in parentheses (), and a colon :. The body of the function is an indented block of instructions. The return statement is used to send a value back to the caller:

def function_name(parameters):
    # function body
    return value

Because of their nature, functions are often referred to as callables, i.e., an object that receives arguments and returns a result. On the other hand, a function signature is defined by its parameters, data types and return type.

Note

A function can optionally be declared with no required parameters. In addition, a function that does not include a return statement returns a None data type by default.

# function declaration
def add(a, b):
    result = a + b
    return result

# function call
print('Result of add(3, 5)  : ', add(3, 5))
print('Result of add(10, -2): ', add(10, -2))
Result of add(3, 5)  :  8
Result of add(10, -2):  8

Functions can return multiple values by separating them with a comma , in the return statement. The returned values are packed into a tuple by default, which can be unpacked into individual variables by assigning multiple names during call:

# function returning multiple values
def min_max(values):
    return min(values), max(values)

# unpacking the returned tuple
vlist = [4, 1, 9, 2, 7]
vmin, vmax = min_max(vlist)

print('Minimum:', vmin)
print('Maximum:', vmax)
Minimum: 1
Maximum: 9

Scope of variables

Variables declared inside a function are local to that function, and cannot be accessed outside of it. Variables declared outside any function are global, and are accessible throughout the program:

# global variables
x = 10
y = 'This is text'

def show_scope():
    # local variable
    y = 20
    print(' Local value of x:', x)  # global x is accessible
    print(' Local value of y:', y)  # local y

# calling function
show_scope()

# printing global variables
print('Global value of x:', x)
print('Global value of y:', y)
 Local value of x: 10
 Local value of y: 20
Global value of x: 10
Global value of y: This is text
Important

Local variables are created when the function is called, and destroyed when it returns. Modifying a global variable inside a function requires the global keyword; otherwise, Python treats the assignment as a local variable.

# global variables
x = 10
y = 'This is text'

def show_scope():
    global y                        # calling global y
    y = 20                          # reassigning global y
    print(' Local value of x:', x)  # global x is accessible
    print(' Local value of y:', y)  # global y is reassigned

# calling function
show_scope()

# printing global variables
print('Global value of x:', x)
print('Global value of y:', y)
 Local value of x: 10
 Local value of y: 20
Global value of x: 10
Global value of y: 20

Positional and keyword arguments

When calling a function, arguments can be passed in two ways:

  • Positional arguments: passed in the same order as the parameters are declared. The position of each argument determines which parameter it maps to during call.
  • Keyword arguments: passed by explicitly naming the parameter. This allows arguments to be supplied in any order during call.
# function declaration
def describe(name, age):
    print(f'{name} is {age} years old.')

# positional arguments
describe('Alice', 30)

# keyword arguments (order does not matter)
describe(age=25, name='Bob')
Alice is 30 years old.
Bob is 25 years old.

Default arguments

Function parameters can be assigned default values during declaration. Thus, if no value is provided for that parameter, the default value is used during call.

Important

Parameters with default values must always be assigned after parameters without defaults; otherwise Python will raise a SyntaxError.

# function with default arguments
def describe(name='Anonymous', age=0):
    print(f'{name} is {age} years old.')

# call without arguments
describe()

# call providing only one argument
describe(name='Alice')

# call providing both positional arguments
describe('Bob', 28)

# call providing both keyword arguments
describe(age=35, name='Carol')
Anonymous is 0 years old.
Alice is 0 years old.
Bob is 28 years old.
Carol is 35 years old.

Arbitrary positional arguments

The *args syntax allows a function to accept an arbitrary number of positional arguments, which is particularly useful when the number of inputs is not known in advance. Inside the function, args is available as a tuple containing all the values passed:

# function with arbitrary positional arguments
def total(*args):
    return sum(args)

# call with individual values
print(total(1))
print(total(1, 2, 3))
1
6

An existing iterable (such as a list or tuple) can also be unpacked into positional arguments using the * operator during call. Note that the iterable arguments must match the expected number of positional arguments declared in the function signature, otherwise a TypeError will be raised:

# call by unpacking a list
values = [1, 2, 3, 4, 5]
print(total(*values))
15

Arbitrary keyword arguments

The **kwargs syntax allows a function to accept an arbitrary number of keyword arguments. Inside the function, kwargs is available as a dictionary which maps parameter names to their respective values.

# function with arbitrary keyword arguments
def show_info(**kwargs):
    for key, value in kwargs.items():
        print(f'{key}: {value}')

# call with keyword arguments directly
show_info(name='Alice', age=30, city='Santiago')
name: Alice
age: 30
city: Santiago

An existing dictionary can also be unpacked into keyword arguments using the ** operator during call. Note that the dictionary keys must match the parameter names expected by the function signature, otherwise a TypeError will be raised:

# unpacking a dictionary into keyword arguments
data = {'name': 'Bob', 'age': 25, 'city': 'Buenos Aires'}
show_info(**data)
name: Bob
age: 25
city: Buenos Aires

Combining argument types

A function can combine regular and arbitrary parameters in a single declaration. The required declaration order is:

  1. Regular positional parameters
  2. Parameters with default values
  3. Arbitrary positional parameters: *args
  4. Arbitrary keyword parameters: **kwargs
Important

This order is enforced by Python. Declaring **kwargs before *args will raise a SyntaxError.

# declaring function combining all parameter types
def profile(name, age=0, *args, **kwargs):
    print('Basic info:')
    print(f'  name : {name}')
    print(f'  age  : {age}')
    if args:
        print('\nAdditional values:')
        for val in args:
            print(f'  - {val}')
    if kwargs:
        print('\nExtra info:')
        for key, val in kwargs.items():
            print(f'  {key}: {val}')

# call with all argument types
extras = {'city': 'Lima', 'occupation': 'engineer'}
profile('Alice', 30, 'hobby: hiking', 'hobby: reading', **extras)
Basic info:
  name : Alice
  age  : 30

Additional values:
  - hobby: hiking
  - hobby: reading

Extra info:
  city: Lima
  occupation: engineer

Lambda functions

A lambda function is a compact, anonymous function defined with the lambda keyword. Lambda functions are limited to a single expression, which is automatically returned. They are commonly used for short operations that are passed as arguments to other functions. The syntax is as follows: \[ \text{lambda }\textit{parameters}:\textit{expression} \]

# lambda function: square of a number
square = lambda x: x**2
print(square(4))

# lambda function with two arguments
add = lambda a, b: a + b
print(add(3, 7))
16
10

Lambda functions are particularly useful when combined with built-in functions such as sorted(), map(), and filter():

# sorting a list of tuples by the second element
pairs        = [(1, 5), (3, 1), (2, 4), (4, 2)]
sorted_pairs = sorted(pairs, key=lambda x: x[1])
print('Sorted by second element:', sorted_pairs)

# applying a function to each element with map()
nums   = [1, 2, 3, 4, 5]
cubes  = list(map(lambda x: x**3, nums))
print('Cubes:', cubes)

# filtering elements with filter()
evens  = list(filter(lambda x: x % 2 == 0, nums))
print('Even numbers:', evens)
Sorted by second element: [(3, 1), (4, 2), (2, 4), (1, 5)]
Cubes: [1, 8, 27, 64, 125]
Even numbers: [2, 4]

Docstrings

Function signatures can be particularly complex, and thus their documentation is recommended. A docstring is a string literal placed immediately after the def line of a function, and is used to document its purpose, parameters, and return values. Docstrings are enclosed in triple quotes ''' or """, and are accessible via the built-in help() function or the __doc__ attribute.

Note

Writing docstrings is considered best practice in Python, as they make functions self-documenting and improve code readability for other users. You can check the PEP 257 for conventions to write docstrings in Python.

def add(a, b):
    """
    Returns the sum of two numbers.

    Parameters
    ----------
    a : int or float
        First number.
    b : int or float
        Second number.

    Returns
    -------
    int or float
        Sum of a and b.
    """
    return a + b

# accessing the docstring
help(add)
Help on function add in module __main__:

add(a, b)
    Returns the sum of two numbers.

    Parameters
    ----------
    a : int or float
        First number.
    b : int or float
        Second number.

    Returns
    -------
    int or float
        Sum of a and b.

Modular programming

Modular programming is the practice of organizing code into reusable, self-contained functions (and eventually modules and packages). It improves code readability, reduces repetition, and makes testing and debugging easier. Constructing a modular program requires experience and practice, but it is rewarding effort for writing effective and maintainable code.

The following example shows how a program that processes a list of temperature values can be structured using functions, each responsible for a single task:

# convert a temperature from Celsius to Fahrenheit
def celsius_to_fahrenheit(c):
    """Converts a temperature from Celsius to Fahrenheit."""
    return 1.8 * c + 32

# classify a temperature value
def classify(temp):
    """Classifies a temperature as Cold, Mild, or Hot."""
    if temp < 10:
        return 'Cold'
    elif temp <= 25:
        return 'Mild'
    else:
        return 'Hot'

# generate a summary report for a list of temperatures
def temperature_report(temps):
    """
    Prints a summary report for a list of temperatures in Celsius,
    including their Fahrenheit equivalent and classification.
    """
    print(f"{'Celsius':>10} {'Fahrenheit':>12} {'Class':>8}")
    print('-' * 33)
    for c in temps:
        f    = celsius_to_fahrenheit(c)
        cls  = classify(c)
        print(f'{c:>10.1f} {f:>12.1f} {cls:>8}')

# main program
readings = [5, 12, 22, 30, -3, 18]
temperature_report(readings)
   Celsius   Fahrenheit    Class
---------------------------------
       5.0         41.0     Cold
      12.0         53.6     Mild
      22.0         71.6     Mild
      30.0         86.0      Hot
      -3.0         26.6     Cold
      18.0         64.4     Mild

Exercises

  1. Declare the following variables:
vals  = [4, 7, 2, 9, 1, 5, 8, 3, 6]
pairs = [('Alice', 88), ('Bob', 74), ('Carol', 95), ('David', 61)]
  1. Write a function stats(values) that receives a list of numbers and returns the minimum, maximum, and mean as a tuple. Call it with vals and print each result.
  2. Write a function normalize(values, vmin, vmax) that receives a list and two boundary values, and returns a new list where each element is scaled to the range [0, 1] using the formula \((x - v_{min}) / (v_{max} - v_{min})\). Use the values returned from the previous exercise as vmin and vmax.
  3. Write a function greet(name, greeting='Hello') with a default argument, and call it three times: once with only name, once changing the greeting, and once using keyword arguments in reverse order.
  4. Write a function cumulative_sum(*args) that accepts an arbitrary number of positional arguments and returns a list with the cumulative sum. Test it with at least two different calls.
  5. Use a lambda function and sorted() to sort pairs by score (second element) from highest to lowest. Print the sorted list.
  6. Use map() with a lambda function to extract only the names from pairs into a new list. Print the result.
  7. Use filter() with a lambda function to create a new list containing only the pairs from pairs where the score is greater than 70. Print the result.
  8. Write a function describe(**kwargs) that accepts any number of keyword arguments and prints each key-value pair on a separate line. Call it by unpacking a dictionary of your choice.
  9. Rewrite the stats function from exercise 2 adding a proper docstring that documents its parameters and return values. Access the docstring using the help() function.

Additional resources