Basic function definition:
Use
def keyword to define a function.
Function names should be lowercase (get_message).
The code inside the function must be indented.
The colon character (:) is required after the function signature.
Function parameters and arguments:
Parameters are variables in the function definition.
Arguments are actual values passed when calling the function.
def hello():
"""A simple function that print a hello message."""
print('Hello!')
# calling hello function
hello()
Output:
Hello!
Return values:
-
Single return value.
def add(i, j):
return i+j
print(add(3, 5)) # Output: 8
-
Multiple return values.
def div(i, j):
return i//j, i%j
quotient, remainder = div(7, 2)
print(f'quotient:{quotient}, remainder:{remainder}') # Output: quotient:3, remainder:1
Positional arguments:
def info(firstname, lastname):
print(f'firstname: {firstname} | lastname: {lastname}')
# call info function with argument 'A' for the parameter firstname and argument 'B' for the parameter lastname
info('fn_A', 'ln_B')
Output:
firstname: fn_A | lastname: ln_B
Keywords arguments:
def info(firstname, lastname):
print(f'firstname: {firstname} | lastname: {lastname}')
# explicitly naming the parameters of the function
info(firstname='fn_C', lastname='ln_D')
# the order of the arguments doesn't matter when explicitly naming the parameters
info(lastname='ln_F', firstname='fn_E')
Output:
firstname: fn_C | lastname: ln_D
firstname: fn_E | lastname: ln_F
Mixing positional and keywords arguments:
Positional arguments must be listed first.
def info(firstname, lastname):
print(f'firstname: {firstname} | lastname: {lastname}')
# mixed positional and keywords arguments
info('fn_G', lastname='ln_H')
Output:
firstname: fn_G | lastname: ln_H
Arbitrary positional arguments (*args):
Passing an arbitrary number of arguments.
# python creates a tuple named values that will contain all the arguments
def print_values(*values):
print(values)
print_values() # Output: ()
print_values(1) # Output: (1,)
print_values(1, 2) # Output: (1, 2)
# unpacking lists
list1 = [1, 2, 3]
print_values(*list1) # Output: (1, 2, 3)
# unpacking tuples
tuple1 = (1, 2, 3)
print_values(*tuple1) # Output: (1, 2, 3)
Mixing regular arguments and *args:
def print_values(key, *values):
print(f'{key}={values}')
# the key argument is required
#print_values() # Raises TypeError: print_values() missing 1 required positional argument: 'key'
print_values('none') # Output: none=()
print_values('one', 'a') # Output: one=('a',)
print_values('two', 'a', 'b') # Output: two=('a', 'b')
Arbitrary keyword arguments (**kwargs)
Passing an arbitrary number of key-value pair as arguments.
# python creates a dictionary named values that will contain all the arguments
def print_values(**values):
print(values)
print_values() # Output: {}
print_values(a=1) # Output: {'a': 1}
print_values(a=1, b=2) # Output: {'a': 1, 'b': 2}
print_values(a=1, b=2, c=3) # Output: {'a': 1, 'b': 2, 'c': 3}
Mixing regular arguments and **kwargs:
Passing regular and arbitrary number of key-value pair as arguments.
def print_values(key, **values):
print(values[key])
# the key argument is required
#print_values('a') # Raises KeyError: 'a'
print_values('a', a=1) # Output: 1
print_values('b', a=1, b=2) # Output: 2
print_values('c', a=1, b=2, c=3) # Output: 3
Combining all parameter types:
def print_values(msg, *args, **kwargs):
print(f'msg:{msg}, args:{args}, kwargs:{kwargs}')
print_values('hello', 1, a=1) # Output: msg:hello, args:(1,), kwargs:{'a': 1}
Default parameters:
def info(firstname, lastname='unknown'):
print(f'firstname: {firstname} | lastname: {lastname}')
# using default with positional argument for the first parameter
info('fn_A') # the second argument is missing, defaults to 'unknown'
# using default with keyword argument for the first parameter
info(firstname='fn_B') # the second argument is missing, defaults to 'unknown'
# overriding the default value
info(firstname='fn_C', lastname='ln_D')
Output:
firstname: fn_A | lastname: unknown
firstname: fn_B | lastname: unknown
firstname: fn_C | lastname: ln_D
Mutable defaults:
You need to avoid mutable defaults like lists or dictionaries.
# using mutable default
def add_message(msg, messages=[]):
messages.append(msg)
return messages
print(add_message("hello")) # Output: ['hello']
print(add_message("python")) # Output: ['hello', 'python']
To fix the issue, use None:
def add_message(msg, messages=None):
if messages is None:
messages = []
messages.append(msg)
return messages
print(add_message("hello")) # Output: ['hello']
print(add_message("python")) # Output: ['python']
Passing lists as arguments:
def pop(x):
return x.pop()
y = [1, 2, 3]
print(pop(y)) # Output: 3
print(y) # Output: [1, 2]
Passing a copy of a list as an argument:
def pop(x):
return x.pop()
y = [1, 2, 3]
print(pop(y[:])) # Output: 3 (passing a list slice as argument)
print(y) # Output: [1, 2, 3]
Working with lists copies:
def append(i, list1, list2, list3):
# modify the original list
list1.append(i)
# create new copy
list_copy = list2.copy()
list_copy.append(i)
# create new copy
list_another_copy = list3[:]
list_another_copy.append(i)
return list1, list_copy, list_another_copy
y1 = [1]
y2 = [2]
y3 = [3]
z1, z2, z3 = append(4, y1, y2, y3)
print(f'y1:{y1}, y2:{y2}, y3:{y3}') # Output: y1:[1, 4], y2:[2], y3:[3]
print(f'z1:{z1}, z2:{z2}, z3:{z3}') # Output: z1:[1, 4], z2:[2, 4], z3:[3, 4]
Local and global variables
global_var = "hello"
def use_global_local():
local_var = "python!"
print(f'{global_var} {local_var}') # Output: hello python!
def update_global():
global global_var
global_var = "hello python!" # the global variable will be changed
print(global_var) # Output: hello python!
def shadow_global():
# shadowing the global variable
global_var = "hello!" # the global variable won't be changed
print(global_var) # Output: hello!
use_global_local()
print(global_var) # Output: hello
update_global()
print(global_var) # Output: hello python!
shadow_global()
print(global_var) # Output: hello python!
Lambda functions
-
Basic lambda:
square = lambda x: x ** 2
print(square(3)) # Output: 9
-
Lambda with multiple arguments:
add = lambda x, y: x + y
print(add(3, 6)) # Output: 9
-
Lambda with map function:
print(list(map(lambda x: x ** 2, [1, 2, 3]))) # Output: [1, 4, 9]
-
Lambda with filter function:
print(list(filter(lambda x: x % 2 == 0, [1, 2, 3]))) # Output: [2]
-
Sorting with lambda:
x = ['abcd', 'abc', 'ab', 'a']
x.sort(key=lambda i: len(i))
print(x) # Output: ['a', 'ab', 'abc', 'abcd']
Using functions as arguments
def apply(operation, numbers):
return [operation(i) for i in numbers]
def square(x):
return x ** 2
print(apply(square, [1, 2, 3])) # Output: [1, 4, 9]
print(apply(lambda x: x ** 2, [1, 2, 3])) # Output: [1, 4, 9]
Return functions
def apply_lambda(multiplier):
return lambda x: x * multiplier
def apply_function(multiplier):
def apply(x):
return x * multiplier
return apply
print(apply_lambda(3)(2)) # Output: 6
print(apply_function(3)(2)) # Output: 6
Modules and imports
-
Creating a module:
Create a file function_module_1.py that contains the definition of two functions:
$ vi function_module_1.py
def add(i, j):
return i+j
def multiply(i, j):
return i*j
-
Import entire module:
Create a file function_import_1.py that imports the module function_module_1 (function_module_1.py):
$ vi function_import_1.py
# all functions defined in the module function_module_1 will be accessible using the prefix function_module_1
import function_module_1
# calling the functions in the module function_module_1 using the name of the module name as a prefix
print(function_module_1.add(2, 3)) # Output: 5
print(function_module_1.multiply(2, 3)) # Output: 6
-
Import specific functions:
Create a file function_import_2.py that imports specific functions defined in the module function_module_1 (function_module_1.py):
$ vi function_import_2.py
# importing the functions add and multiply from the module function_module_1
from function_module_1 import add, multiply
# calling directly the functions in the module function_module_1 without specifying the module name
print(add(2, 3)) # Output: 5
print(multiply(2, 3)) # Output: 6
-
Import all:
Create a file function_import_3.py that imports all functions defined in the module function_module_1 (function_module_1.py):
$ vi function_import_3.py
# importing all functions from the module function_module_1
from function_module_1 import *
# calling directly the functions in the module function_module_1 without specifying the module name
print(add(2, 3)) # Output: 5
print(multiply(2, 3)) # Output: 6
-
Import with alias (as):
Create a file function_import_4.py that imports the module function_module_1 (function_module_1.py) and give it an alias f1:
$ vi function_import_4.py
# defining the alias f1 for the module function_module_1
import function_module_1 as f1
# calling the functions in the module function_module_1 using the alias of the module as a prefix
print(f1.add(2, 3)) # Output: 5
print(f1.multiply(2, 3)) # Output: 6
-
Import specific functions with aliases (as):
Create a file function_import_5.py that imports specific functions defined in the module function_module_1 (function_module_1.py) and give them aliases:
$ vi function_import_5.py
# defining the alias a for the method add and the alias m for the method multiply
from function_module_1 import add as a, multiply as m
# calling directly the functions in the module function_module_1 using the specified alias for each function
print(a(2, 3)) # Output: 5
print(m(2, 3)) # Output: 6