def identity(a, b): # only positional arguments
return a, b
6 Functions
Functions encapsulate pieces of logic that we want to use repeatedly. Also, this encapsulation makes testing our code much easier which is rather useful.
6.1 Basic syntax
1,2) identity(
(1, 2)
def identity_with_default(a, b=1): # b is a so called "keyword-argument"
return a, b
"hello") # argument b is optional, if not passed, the default is used identity_with_default(
('hello', 1)
Python functions always return a value, even without a return
statement, in which case the return value will be None
.
def implicit_return():
pass # do literally nothing
= implicit_return() out
type(out) out,
(None, NoneType)
This three definitions are equivalent:
def g():
print("hello")
return None
def h():
print("hello")
return
def f():
print("hello")
# The return statement is implicitly here
6.2 Arbitrary arguments: *args
& **kwargs
We can write a function with arbitrary arguments. For that, we use the syntax *args
. The args
will be put into a tuple:
def print_args(*args):
print(type(args))
for arg in args:
print(arg)
1,2) print_args(
<class 'tuple'>
1
2
1, 2, 3, 4) # we can pass as many as we want! print_args(
<class 'tuple'>
1
2
3
4
This logic extends to key-word arguments. We use the syntax **kwargs
for that. Since key-words are pairs, they are put into a dictionary (instead of a tuple):
def print_kwargs(**kwargs): # only positional arguments
for k, v in kwargs.items(): # it's just a dict!
print(k, "->", v)
="martinistraße", number="52") print_kwargs(street
street -> martinistraße
number -> 52
="martinistraße", number="52", coolness="very-high") print_kwargs(street
street -> martinistraße
number -> 52
coolness -> very-high
We can combine both *args
and **kwargs
. This function will take any arguments we pass:
def general(*args, **kwargs):
print(args)
print(kwargs)
1, 2, first="hello", second="world") general(
(1, 2)
{'first': 'hello', 'second': 'world'}
6.3 Functions are values
We can assign functions to variables and pass them around, like any other object (people call this to have “functions as first-class citizen”).
def printer(func):
= func() # call whatever function we pass
out print(out) # print the output
def greeting():
return "hello from greeting func"
printer(greeting)
hello from greeting func
= greeting # Notice we are not calling it with () f
f
<function __main__.greeting()>
printer(f)
hello from greeting func
6.4 Anonymous functions
There is a shorthand to define functions with this syntax:
lambda [optional-args]: [return-values]
lambda: "say hello"
<function __main__.<lambda>()>
lambda: "hello course") printer(
hello course
It can also take arguments:
= lambda x: x+1 f
1) f(
2
A typical use case of anonymous functions:
= ["anna", "lui", "marco", "ramiro", "tim"] names
sorted(names, key=lambda name: name[-1])
['anna', 'lui', 'tim', 'marco', 'ramiro']
sorted(names, key=lambda name: len(name))
['lui', 'tim', 'anna', 'marco', 'ramiro']
= [("anna", 93), ("lui", 19), ("marco", 11), ("ramiro", 83)] ages
sorted(ages, key=lambda name_age: name_age[1]) # name_age is a tuple
[('marco', 11), ('lui', 19), ('ramiro', 83), ('anna', 93)]
6.5 Early return
Python functions have so-called early return, which means a function will exit as soon as it hits the first return
statement, for example:
def early():
if 1 > 0:
return "first condition"
if 2 > 0: # This code will never be evaluated
return "second condition"
early()
'first condition'
This can help us to simplify code, for example, this two definitions are equivalent:
def f(x):
if x == 1:
return "it's 1"
elif x == 2:
return "it's 2"
elif x == 3:
return "it's 3"
else:
return "not 1,2,3"
def ff(x):
if x == 1:
return "it's 1"
if x == 2:
return "it's 2"
if x == 3:
return "it's 3"
return "not 1,2,3"
2), f(4) f(
("it's 2", 'not 1,2,3')
2), ff(4) ff(
("it's 2", 'not 1,2,3')
6.6 Keyword-only arguments
There’s a way to force the arguments of a function to be keyword-only, which can be useful to avoid mistakes and kindly nudge the users of our code (yes, you yourself too!) to pass the arguments to a function explicitly.
Everything coming after *
must be key-word:
def func(a, b, *, c, d):
print(a, b, c, d)
This will not work (pay attention to the error message):
"hi", "there") func(
TypeError: func() missing 2 required keyword-only arguments: 'c' and 'd'
Neither will this:
"hi", "there", "dear", "students") func(
TypeError: func() takes 2 positional arguments but 4 were given
"hi", "there", "dear", d="students") func(
TypeError: func() takes 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
Only passing c
and d
explicitly will do:
"hi", "there", c="dear", d="students") func(
hi there dear students
6.7 Exercises
- Write a function called
intro
that takes two positional arguments, name and age, and prints a sentence introducing a person. The function should return nothing. - Repeat it, but adding an optional argument, city. The function should now return the introducing string (consider handling the city)
- Write a function called
sort_dict_by_value
that takes a dictionary and returns a dictionary sorted by value. - Write a function that takes an arbitrary number of key-word only arguments representing pairs (name, age) and returns a list of tuples sorted by age. For example:
# For this pairs
=32, mark=12)
your_function(lua# Should output:
"mark", 12), ("lua", 32)] [(
The very same function should be able to deal with other number of arguments, eg:
=32, mark=12, anna=42)
your_function(lua# Should output:
"mark", 12), ("lua", 32), ("anna", 42)] [(
Hint: Try to use the function sort_dict
that you wrote in the previous point.
- Write a function that takes 2 arguments
items
(a container of elements,no matter what they are) andsorting_func
(a function that will somehow sort the elements ofitems
) and returns the result of the sorting function applied to items. For example, your function should behave like this:
3, 2, 1], sorted)
your_function([# Should output:
1, 2, 3]
[
"a": 2, "b": 1}, sort_dict)
your_function({# Should output:
"b": 1, "a": 2} {