Result of add(3, 5) : 8
Result of add(10, -2): 8
03. Functions
Computational methods
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.
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.
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:
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
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.
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.
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:
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:
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.
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:
Combining argument types
A function can combine regular and arbitrary parameters in a single declaration. The required declaration order is:
- Regular positional parameters
- Parameters with default values
- Arbitrary positional parameters:
*args - Arbitrary keyword parameters:
**kwargs
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} \]
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.
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.
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
- Declare the following variables:
- Write a function
stats(values)that receives alistof numbers and returns the minimum, maximum, and mean as atuple. Call it withvalsand print each result. - Write a function
normalize(values, vmin, vmax)that receives alistand two boundary values, and returns a newlistwhere 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 asvminandvmax. - Write a function
greet(name, greeting='Hello')with a default argument, and call it three times: once with onlyname, once changing the greeting, and once using keyword arguments in reverse order. - Write a function
cumulative_sum(*args)that accepts an arbitrary number of positional arguments and returns alistwith the cumulative sum. Test it with at least two different calls. - Use a
lambdafunction andsorted()to sortpairsby score (second element) from highest to lowest. Print the sortedlist. - Use
map()with alambdafunction to extract only the names frompairsinto a newlist. Print the result. - Use
filter()with alambdafunction to create a newlistcontaining only the pairs frompairswhere the score is greater than 70. Print the result. - 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 adictionaryof your choice. - Rewrite the
statsfunction from exercise 2 adding a proper docstring that documents its parameters and return values. Access the docstring using thehelp()function.