Introduction
Why Use Functions in Python?
- Functions facilitate easy code re-use.
- They help break up complex code into easier-to-understand chunks.
Parameter vs Argument
- Parameter: What is declared in the function definition.
- Argument: What is passed when calling the function.
Functions as Python Objects
- Functions possess state (name, code, parameters, etc.).
- They are callable and always return something when called.
- Functions can be assigned to a symbol.
- They can be passed as parameters to other functions.
- Functions can be returned from a function call.
Callables
- An object is callable if it can be invoked using
()
. - Functions are called by invoking them, but other object types can also be callable.
# Calling Functions
print('hello')
math.sqrt(4)
# Calling a method on the object
my_list.copy()
# Calling the object itself - callable object
range(100)
Custom Functions
Functions are defined using the def
keyword.
def function_name():
# Indented block
...
return <value>
- The
def
keyword signifies the definition of a function. - The function’s name can be any valid Python identifier.
- The function body contains any valid Python code.
- Functions always return a value, defaulting to
None
if not specified.
Example Without Return Value
def say_hello():
print('Hello!')
result = say_hello()
print(result)
# Output:
# Hello!
# None
Example With Return Value
def one():
return 1
result = one()
print(result)
# Output:
# 1
Passing Values to Functions (by Position)
def add(a, b): # Specify the parameters
print(f'a = {a}')
print(f'b = {b}')
return a + b
add(2, 3)
# Output:
# a = 2
# b = 3
# 5
Positional arguments are passed in the order of parameters.
Namespaces
A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries. – Python glossary
A namespace is created when a function is called, and it only lasts until the function returns.
Coding
Passing values as named arguments
def gen_matrix(rows, cols, default_value):
return [[default_value for i in range(cols)] for j in range(rows)]
gen_matrix(cols=2, rows=2, default_value=1)
# Output:
# [[1, 1], [1, 1]]
Passing values with a mix of positional and named arguments
def gen_matrix(rows, cols, default_value):
return [[default_value for i in range(cols)] for j in range(rows)]
gen_matrix(2, cols=2, default_value=1)
# Output:
# [[1, 1], [1, 1]]
An error occurs due to multiple values assigned to the same argument:
def gen_matrix(rows, cols, default_value):
return [[default_value for i in range(cols)] for j in range(rows)]
gen_matrix(1, rows=2, default_value=0)
Output:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[11], line 4
1 def gen_matrix(rows, cols, default_value):
2 return [[default_value for i in range(cols)] for j in range(rows)]
----> 4 gen_matrix(1, rows=2, default_value=0)
TypeError: gen_matrix() got multiple values for argument 'rows'
Once passing value starts using named arguments in a call, all subsequent arguments must be named too:
def gen_matrix(rows, cols, default_value):
return [[default_value for i in range(cols)] for j in range(rows)]
gen_matrix(2, rows=2, 0)
Output:
Cell In[15], line 4
gen_matrix(2, rows=2, 0)
^
SyntaxError: positional argument follows keyword argument
Star Arguments
When defining a function, the *
can be used to capture an unlimited number of positional arguments given to the function. These arguments are captured into a tuple.
def average(*values):
print(type(values))
print(values)
average(1, 2, 3)
# Output:
# <class 'tuple'>
# (1, 2, 3)
Using *args
for an extra argument + variable number of arguments
def my_func(a, b, *others):
print(a)
print(b)
print(others)
my_func(1, 2, 3, 4, 5)
# Output:
# 1
# 2
# (3, 4, 5)
def my_func(a, b, *others, c):
print(a)
print(b)
print(others)
print(c)
my_func(1, 2, 3, 4, 5)
Output:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 7
4 print(others)
5 print(c)
----> 7 my_func(1, 2, 3, 4, 5)
TypeError: my_func() missing 1 required keyword-only argument: 'c'
def my_func(a, b, *others, c):
print(a)
print(b)
print(others)
print(c)
my_func(1, 2, 3, 4, c=5)
# Output:
# 1
# 2
# (3, 4)
# 5
Using *args
in Function Calls
When calling a function, the *
can be used to unpack an iterable into the arguments in the function call.
def my_func(*numbers):
print(numbers)
for num in numbers:
print(num)
L = [1, 2, 3]
my_func(L, 5)
# Output:
# ([1, 2, 3], 5)
# [1, 2, 3]
# 5
def my_func(*numbers):
print(numbers)
for num in numbers:
print(num)
L = [1, 2, 3]
my_func(*L, 5)
# Output:
# (1, 2, 3, 5)
# 1
# 2
# 3
# 5