Functions 1 - introduction
Download exercises zip
Introduction
A function is some code which takes some parameters and uses them to produce or report some result.
In this notebook we will see how to define functions to reuse code, and talk about variables scope.
What to do
unzip exercises in a folder, you should get something like this:
functions
fun1-intro.ipynb
fun1-intro-sol.ipynb
fun2-errors-and-testing.ipynb
fun2-errors-and-testing-sol.ipynb
fun3-strings.ipynb
fun3-strings-sol.ipynb
fun4-lists.ipynb
fun4-lists-sol.ipynb
fun5-tuples.ipynb
fun5-tuples-sol.ipynb
fun6-sets.ipynb
fun6-sets-sol.ipynb
fun7-dictionaries.ipynb
fun7-dictionaries-sol.ipynb
fun8-chal.ipynb
jupman.py
WARNING: to correctly visualize the notebook, it MUST be in an unzipped folder !
open Jupyter Notebook from that folder. Two things should open, first a console and then browser. The browser should show a file list: navigate the list and open the notebook
functions/fun1-intro.ipynb
Go on reading that notebook, and follow instuctions inside.
Shortcut keys:
to execute Python code inside a Jupyter cell, press
Control + Enter
to execute Python code inside a Jupyter cell AND select next cell, press
Shift + Enter
to execute Python code inside a Jupyter cell AND a create a new cell aftwerwards, press
Alt + Enter
If the notebooks look stuck, try to select
Kernel -> Restart
Why functions?
We may need functions for a lot of reasons, including:
Reduce code duplication: put in functions parts of code that are needed several times in the whole program, so you don’t need to repeat the same code over and over again;
Decompose a complex task: make the code easier to write and understand by splitting the whole program in several easier functions;
Function definition - questions
For each of the following expressions, try guessing the result it produces (or if it gives error)
def f(): print('car') print(f())
def f(): print('car') print(f())
def f(): return 3 print(f())
def f(): return 3 print(f())
def f() return 3 print(f())
def f(): return 3 print(f()f())
def f(): return 3 print(f()*f())
def f(): pass print(f())
def f(x): return x print( f() )
def f(x): return x print( f(5) )
def f(): print('fire') x = f() print(x)
def f(): return(print('fire')) print(f())
def f(x): return 'x' print(f(5))
def f(x): return x print(f(5))
def etc(): print('etc...') return etc() etc()
def gu(): print('GU') ru() def ru(): print('RU') gu() gu()
Different function kinds
You can roughly find 5 different function kinds in the wild:
PRODUCES SIDE EFFECTS: PRINTS / ASKS MANUAL INPUT / WRITES by modifying the environment in some way - examples: printing characters on the screen, asking interactively input from the user, writing into a file
RETURNS a value, either as NEW memory region or a pointer to an existing memory region
MODIFIES the input
MODIFIES the input and RETURNS it (allows for call chaining)
MODIFIES the input and RETURNS something derived from it
Let’s try now to understand the differences with various examples.
SIDE EFFECTS
Only PRINTS / ASKS INTERACTIVE INPUT / WRITES INTO A FILE
DOES NOT modify the input!
DOES NOT return anything!
Example:
[2]:
def printola(lst):
"""PRINTS the first two elements of the given list
"""
print('The first two elements are', lst[0], lst[1])
la = [8,5,6,2]
printola(la)
jupman.pytut()
The first two elements are 8 5
[2]:
RETURN
RETURN some value, either as NEW memory region or a pointer to an existing memory region according to the function text
DOES NOT modify the input
DOES NOT print anything!
Example:
[3]:
def returnola(lst):
"""RETURN a NEW list having all the numbers doubled
"""
ret = []
for el in lst:
ret.append(el*2)
return ret
la = [5,2,6,3]
res = returnola(la)
print("la :", la)
print("res:", res)
jupman.pytut()
la : [5, 2, 6, 3]
res: [10, 4, 12, 6]
[3]:
MODIFY
MODIFY the input. By MODIFYING, we typically mean changing data inside existing memory regions, limiting as much as possible the creation of new ones.
DOES NOT return anything!
DOES NOT print anything!
DOES NOT create new memory regions (or limits the creation to the bare needed)
Example:
[4]:
def modifanta(lst):
"""MODIFIES lst by ordering it in-place
"""
lst.sort()
la = [43434]
la = [7,4,9,8]
modifanta(la)
print("la:", la)
jupman.pytut()
la: [4, 7, 8, 9]
[4]:
MODIFY and RETURN
MODIFIES the input and RETURNS a pointer to it
DOES NOT print anything!
DOES NOT create new memory regions (or limits the creation to the bare needed)
Note: allows call chaining
[5]:
def modiret(lst):
"""MODIFY lst by doubling all its elements, and finally RETURNS it
"""
for i in range(len(lst)):
lst[i] = lst[i] * 2
return lst
la = [8,7,5]
res = modiret(la)
print("res :", res) # [16,14,10] RETURNED the modified input
print("la :", la) # [16,14,10] la input was MODIFIED !!
print()
lb = [7,5,6]
modiret(lb).reverse() # NOTE WE CAN CONCATENATE
print("lb :", lb) # [12,10,14] lb input was MODIFIED !!
#modiret(lb).reverse().append(16) # ... but this wouldn't work. Why?
jupman.pytut()
res : [16, 14, 10]
la : [16, 14, 10]
lb : [12, 10, 14]
[5]:
MODIFY AND RETURN A PART
MODIFY the input and RETURN a part of it
DOES NOT print anything!
[6]:
def modirip(lst):
"""MODIFY lst by sorting it and removing the greatest element. Finally, RETURN the removed element.
"""
lst.sort()
ret = lst[-1]
lst.pop()
return ret
la = ['b','c','a']
res = modirip(la)
print("res :", res) # 'c' RETURNED a piece of the input
print("la :", la) # ['a','b'] la was MODIFIED!!
jupman.pytut()
res : c
la : ['a', 'b']
[6]:
Remember the commandments
III COMMANDMENT
You shall never ever reassign function parameters
Never perform any of these assignments, as you risk losing the parameter passed during function call:
[7]:
def sin(my_int):
my_int = 666 # you lost the 5 passed from external call!
print(my_int) # prints 666
x = 5
sin(x)
666
Same reasoning can be applied to all other types:
[8]:
def evil(my_string):
my_string = "666"
[9]:
def disgrace(my_list):
my_list = [666]
[10]:
def delirium(my_dict):
my_dict = {"evil":666}
For the sole case when you have composite parameters like lists or dictionaries, you can write like below IF AND ONLY IF the function description requires to MODIFY the internal elements of the parameter (like for example sorting a list in-place or changing the field of a dictionary).
[11]:
# MODIFY my_list in some way
def allowed(my_list):
my_list[2] = 9 # OK, function text requires it
outside = [8,5,7]
allowed(outside)
print(outside)
[8, 5, 9]
[12]:
# MODIFY dictionary in some way
def ok(dictionary):
dictionary["my field"] = 5 # OK, function text requires it
[13]:
# MODIFY instance in some way
def fine(class_instance):
class_instance.my_field = 7 # OK, function text requires it
On the other hand, if the function requires to RETURN a NEW object, you shall not fall into the temptation of modifying the input:
[14]:
# RETURN a NEW sorted list
def pain(my_list):
my_list.sort() # BAD, you are modifying the input list instead of creating a new one!
return my_list
[15]:
# RETURN a NEW list
def crisis(my_list):
my_list[0] = 5 # BAD, as above
return my_list
[16]:
# RETURN a NEW dictionary
def torment(my_dict):
my_dict['a'] = 6 # BAD, you are modifying the input dictionary instead of creating a new one!
return my_dict
[17]:
# RETURN a NEW class instance
def desperation(my_instance):
my_instance.my_field = 6 # BAD, you are modifying the input object
# instead of creating a new one!
return my_instance
IV COMMANDMENT
You shall never ever reassign values to function calls or methods
WRONG:
my_function() = 666
my_function() = 'evil'
my_function() = [666]
CORRECT:
x = 5
y = my_fun()
z = []
z[0] = 7
d = dict()
d["a"] = 6
Function calls like my_function()
return calculations results and store them in a box in memory which is only created for the purposes of the call, and Python will not allow us to reuse it like it were a variabile.
Whenever you see name()
in the left part, it cannot be followed by the equality sign =
(but it can be followed by two equals sign ==
if you are doing a comparison).
V COMMANDMENT
You shall never ever redefine system functions
Python has several system defined functions. For example list
is a Python type: as such, you can use it for example as a function to convert some type to a list:
[18]:
list("ciao")
[18]:
['c', 'i', 'a', 'o']
When you allow the forces of evil to take the best of you, you might be tempted to use reserved words like list
as a variable for you own miserable purposes:
list = ['my', 'pitiful', 'list']
Python allows you to do so, but we do not, for the consequences are disastrous.
For example, if you now attempt to use list
for its intended purpose like casting to list, it won’t work anymore:
list("ciao")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-c63add832213> in <module>()
----> 1 list("ciao")
TypeError: 'list' object is not callable
In particular, we recommend to not redefine these precious functions:
bool
,int
,float
,tuple
,str
,list
,set
,dict
max
,min
,sum
next
,iter
id
,dir
,vars
,help
Immutable values
Basic types such integers, float, booleans are immutable, as well as some sequences like strings and tuples : when you are asked to RETURN one of these types, say a string, the only thing you can do is obtaining NEW strings based upon the parameters you receive. Let’s see an example.
Suppose we are asked to implement this function:
Write a function
my_upper
which RETURNS the passed string as uppercase.
We could implement it like this:
[19]:
# Run this cell to have Python Tutor working
import jupman;
[20]:
external_string = "sailor"
def my_upper(s):
ret = s.upper() # string methods create NEW string
return ret
result = my_upper(external_string)
print(' result:', result)
print('external_string:', external_string)
jupman.pytut()
result: SAILOR
external_string: sailor
[20]:
Notice some things:
the
external_string
didn’t changewe didn’t write
s =
inside the function body, as the IV COMMANDMENT prescribes not to reassign parameterswe didn’t refer to
external_string
inside the function body: doing so would have defeated the purpose of functions, which is to isolate them from outside world.
Changing the world: fail / 1
What if we actually did want to change the assignment of external_string
?
You might be tempted to write something like an assignment s =
right inside the function. The following code will not work.
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[21]:
external_string = "sailor"
def my_upper(s):
s = s.upper()
return s
result = my_upper(external_string)
print(' result:', result)
print('external_string:', external_string)
jupman.pytut()
result: SAILOR
external_string: sailor
[21]:
Changing the world: fail / 2
Let’s see another temptation. You might try to assign external_string =
right inside the function. The follwing code again will not work
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[22]:
external_string = "sailor"
def my_upper(s):
external_string = s.upper()
return external_string
result = my_upper(external_string)
print(' result:', result)
print('external_string:', external_string)
jupman.pytut()
result: SAILOR
external_string: sailor
[22]:
Changing the world: success!
The proper way to tackle the problem is to create a NEW string inside the function, return it, and then outside the function perform the assignment external_string = result
.
[23]:
external_string = "sailor"
def my_upper(s):
ret = s.upper()
return ret
result = my_upper(external_string)
external_string = result # reassignes *outside*
print(' result:', result)
print('external_string:', external_string)
jupman.pytut()
result: SAILOR
external_string: SAILOR
[23]:
global
keyword
If we really wanted to modify external_string
association inside the function, we could still do it with global keyword, but typically it’s best to avoid using it to keep the code clean.
Mutable values
Sequences like lists, sets, dictionaries are mutable objects. When you call a function and pass one of these objects, Python actually gives the function only a reference to the object: a very small pointer which is just an arrow pointing to the memory region where the actual object resides. Since the function only receives a small pointer, calling the function is a fast operation. On the other side, we need to be aware that since no copy of the whole data structure is performed, inside the function it will be like operating on the original memory region which lives outside the function call.
All of this may feel like a bit of a mouthful. Let’s see a practical example in Python Tutor.
Let’s say we need to implement this function:
Write a function which takes a list and MODIFIES it by doubling all of its numbers
Note in the text we used the word MODIFIES, meaning we really want to change the original memory region of the external object we are given.
As simple as it might seem, there are many ways to get this wrong. Let’s see some.
Doubling: fail / 1
You might be tempted to solve the problem like the following code, but it will not work.
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[24]:
external_numbers = [10,20,30]
def double(lst):
for element in lst:
element = element * 2
double(external_numbers)
jupman.pytut()
[24]:
Doubling: fail / 2
You might have another temptation to solve the problem like the following code, but again it will not work.
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[25]:
external_numbers = [10,20,30]
def double(lst):
tmp = []
for element in lst:
tmp.append(element * 2)
lst = tmp
double(external_numbers)
jupman.pytut()
[25]:
Doubling: fail / 3
You might be tempted to solve the problem also like in the following code, but again it will not work.
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[26]:
external_numbers = [10,20,30]
def double(lst):
tmp = []
for element in lst:
tmp.append(element * 2)
external_numbers = tmp
double(external_numbers)
jupman.pytut()
[26]:
Doubling: fail / 4
Let’s see the final temptation, which yet again will not work.
QUESTION: Why? Try to answer before checking execution in Python Tutor.
Show answer[27]:
external_numbers = [10,20,30]
def double(lst):
tmp = []
for element in lst:
tmp.append(element * 2)
return tmp
external_numbers = double(external_numbers)
jupman.pytut()
[27]:
Probably you are a bit confused about the previous attempt, which to the untrained eye might look successful. Let’s try to rewrite it with one variable more saved
which will point to exactly the same original memory region of external_numbers
. You will see that at the end saved
will point to [10,20,30]
, showing we didn’t actually MODIFY the original region.
[28]:
external_numbers = [10,20,30]
saved = external_numbers # we preserve a pointer
def double(lst):
tmp = []
for element in lst:
tmp.append(element * 2)
return tmp
external_numbers = double(external_numbers)
print('external_numbers:', external_numbers) # [20,40,60]
print(' saved:', saved) # [10,20,30]
jupman.pytut()
external_numbers: [20, 40, 60]
saved: [10, 20, 30]
[28]:
Doubling success!
Let’s finally see the right way to do it: we need to consider we want to refer to original cells, so to do it properly we need to access them by index, and we will need a for in
range.
[29]:
external_numbers = [1,2,3,4,5]
def double(lst):
for i in range(len(lst)):
lst[i] = lst[i] * 2
double(external_numbers)
jupman.pytut()
[29]:
Notice that:
when the function call frame is created, we see an arrow to the original data
the
external_list
actually changed, without ever reassigning it (not even outside)we didn’t reassign
lst =
inside the function body, as the IV COMMANDMENT prescribes not to reassign parameterswe didn’t use
return
, as the function text told us nothing about returningwe didn’t referred to
external_list
inside the function body: doing so would have defeated the purpose of functions, which is to isolate them from outside world.
In general, in the case of mutable data data isolation is never tight, as we get pointers to data living outside the function frame. When we manipulate pointers it’s really up to us to take special care.
Modifying parameters - Questions
For each of the following expressions, try guessing the result it produces (or if it gives error)
def zam(bal): bal = 4 x = 8 zam(x) print(x)
def zom(y): y = 4 y = 8 zom(y) print(y)
def per(la): la.append('è') per(la) print(la)
def zeb(lst): lst.append('d') la = ['a','b','c'] zeb(la) print(la)
def beware(la): la = ['?','?'] lb = ['d','a','m','n'] beware(lb) print(lb)
def umpa(string): string = "lompa" word = "gnappa" umpa(word) print(word)
def sporty(diz): diz['sneakers'] = 2 cabinet = {'rackets':4, 'balls': 7} sporty(cabinet) print(cabinet)
def numma(lst): lst + [4,5] la = [1,2,3] print(numma(la)) print(la)
def jar(lst): return lst + [4,5] lb = [1,2,3] print(jar(lb)) print(lb)
Exercises - Changing music
It’s time to better understand what we’re doing when we mess with variables and function calls.
An uncle of ours gave us a dusty album
of songs (for some reason tens of years have passed since he last turned on the radio)
[30]:
album = [
"Caterina Caselli - Cento giorni",
"Delirium - Jesahel",
"Jan Hammer - Crockett's Theme",
"Sonata Arctica - White Pearl, Black Oceans",
"Lucio Dalla - 4 marzo 1943.mp3",
"The Wellermen - Wellerman",
"Manu Chao - Por el Suelo",
"Intillimani - El Pueblo Unido"
]
Songs are reported with the group, a dash -
and finally the name. Strong with our new knowledge about functions, we decide to put in practice modern software development practices to analyze these misterious relics of the past.
In the following you will find several exercises which will ask you to develop functions: making something which seems to work is often easy, the true challenge is following exactly what is asked in function text: take particular care about capitalized words, like PRINT, MODIFY, RETURN, and to the desired outputs, trying to understand to which category the various functions belong to.
Exercises must all be solved following this scheme:
album = ...
def func(songs):
# do something with songs, NOT with album
# ....
func(album) # calls to test, external to function body
DO NOT WRITE EXTERNAL VARIABLE NAMES INSIDE THE FUNCTION
In particular:
DO NOT reassign
album =
DO NOT call its methods
album.some_method()
A function must be typically seen as an isolated world, which should interact with the outworld ONLY through the given parameters. By explicitly writing album
, you would override such isolation bringing great misfortune.
ALWAYS USE A PARAMETER NAME DIFFERENT FROM EXTERNAL VARIABLES
For example, if external data is called album
, you can call the parameter songs
Exercise - show
Write a function which given a list songs
, PRINTS the group justified to the right followed by a :
and the song name
HINT: to justify the text, use the string method .rjust(16)
>>> res = show(album) # only prints, implicitly returns None
Caterina Caselli: Cento giorni
Delirium: Jesahel
Jan Hammer: Crockett's Theme
Sonata Arctica: White Pearl, Black Oceans
Lucio Dalla: 4 marzo 1943.mp3
The Wellermen: Wellerman
Manu Chao: Por el Suelo
Intillimani: El Pueblo Unido
>>> print(res)
None
[31]:
album = [
"Caterina Caselli - Cento giorni",
"Delirium - Jesahel",
"Jan Hammer - Crockett's Theme",
"Sonata Arctica - White Pearl, Black Oceans",
"Lucio Dalla - 4 marzo 1943.mp3",
"The Wellermen - Wellerman",
"Manu Chao - Por el Suelo",
"Intillimani - El Pueblo Unido"
]
# write here
Exercise - record
Write a function which given two lists songsA
and songsB
, MODIFIES songsA
overwriting it with the content of songsB
. If songsB
has less elements than songsS
, fill the remanining spaces with None
ASSUME
songsB
has at most the same number of songs ofsongsA
DO NOT reassign
album
(so noalbum =
)
# returns nothing!
>>> record(album, ["Toto Cotugno - L'Italiano vero", "Mia Martini - Minuetto", "Al Bano-Nel sole"])
>>> album # parameeter was modified
["Toto Cotugno - L'Italiano vero",
"Mia Martini - Minuetto",
"Al Bano - Nel sole",
None,
None,
None,
None,
None]
[33]:
album = [
"Caterina Caselli - Cento giorni",
"Delirium - Jesahel",
"Jan Hammer - Crockett's Theme",
"Sonata Arctica - White Pearl, Black Oceans",
"Lucio Dalla - 4 marzo 1943.mp3",
"The Wellermen - Wellerman",
"Manu Chao - Por el Suelo",
"Intillimani - El Pueblo Unido"
]
# write here
Exercise - great
Write a function great
which given a list of songs MODIFIES the list by uppercasing all the characters, and then RETURNS it
DO NOT reassign
album
(noalbum =
)
Example:
>>> great(album) # return
['CATERINA CASELLI - CENTO GIORNI',
'DELIRIUM - JESAHEL',
"JAN HAMMER - CROCKETT'S THEME",
'SONATA ARCTICA - WHITE PEARL, BLACK OCEANS',
'LUCIO DALLA - 4 MARZO 1943.MP3',
'THE WELLERMEN - WELLERMAN',
'MANU CHAO - POR EL SUELO',
'INTILLIMANI - EL PUEBLO UNIDO']
>>> album # parameter was modified
['CATERINA CASELLI - CENTO GIORNI',
'DELIRIUM - JESAHEL',
"JAN HAMMER - CROCKETT'S THEME",
'SONATA ARCTICA - WHITE PEARL, BLACK OCEANS',
'LUCIO DALLA - 4 MARZO 1943.MP3',
'THE WELLERMEN - WELLERMAN',
'MANU CHAO - POR EL SUELO',
'INTILLIMANI - EL PUEBLO UNIDO']
[34]:
album = [
"Caterina Caselli - Cento giorni",
"Delirium - Jesahel",
"Jan Hammer - Crockett's Theme",
"Sonata Arctica - White Pearl, Black Oceans",
"Lucio Dalla - 4 marzo 1943.mp3",
"The Wellermen - Wellerman",
"Manu Chao - Por el Suelo",
"Intillimani - El Pueblo Unido"
]
# write here
Exercise - shorten
Write a function shorten
which given a list of songs
and a number n
, MODIFIES songs
so it has only n
songs, then RETURNS a NEW list with all the removed elements.
if
n
is too large, returns an empty list without modifying the albumUSE a parameter name different from
album
DO NOT reassign
album
(so noalbum =
)
Example:
>>> shorten(album, 3) # returns
['Sonata Arctica - White Pearl, Black Oceans',
'Lucio Dalla - 4 marzo 1943.mp3',
'The Wellermen - Wellerman',
'Manu Chao - Por el Suelo',
'Intillimani - El Pueblo Unido']
>>> album # the parameter was modified
['Caterina Caselli - Cento giorni',
'Delirium - Jesahel',
"Jan Hammer - Crockett's Theme"]
>>> shorten(album, 7)
[]
>>> album
['Caterina Caselli - Cento giorni',
'Delirium - Jesahel',
"Jan Hammer - Crockett's Theme"]
[35]:
album = [
"Caterina Caselli - Cento giorni",
"Delirium - Jesahel",
"Jan Hammer - Crockett's Theme",
"Sonata Arctica - White Pearl, Black Oceans",
"Lucio Dalla - 4 marzo 1943.mp3",
"The Wellermen - Wellerman",
"Manu Chao - Por el Suelo",
"Intillimani - El Pueblo Unido"
]
# write here
Lambda functions
Lambda functions are functions which:
have no name
are defined on one line, typically right where they are needed
their body is an expression, thus you need no
return
Let’s create a lambda function which takes a number x
and doubles it:
[36]:
lambda x: x*2
[36]:
<function __main__.<lambda>(x)>
As you see, Python created a function object, which gets displayed by Jupyter. Unfortunately, at this point the function object got lost, because that is what happens to any object created by an expression that is not assigned to a variable.
To be able to call the function, we will thus convenient to assign such function object to a variable, say f
:
[37]:
f = lambda x: x*2
[38]:
f
[38]:
<function __main__.<lambda>(x)>
Great, now we have a function we can call as many times as we want:
[39]:
f(5)
[39]:
10
[40]:
f(7)
[40]:
14
So writing
[41]:
def f(x):
return x*2
or
[42]:
f = lambda x: x*2
are completely equivalent forms, the main difference being with def
we can write functions with bodies on multiple lines. Lambdas may appear limited, so why should we use them? Sometimes they allow for very concise code. For example, imagine you have a list of tuples holding animals and their lifespan:
[43]:
animals = [('dog', 12), ('cat', 14), ('pelican', 30), ('eagle', 25), ('squirrel', 6)]
If you want to sort them by lifespan, you can try the .sort
method but it will not work:
[44]:
animals.sort()
[45]:
animals
[45]:
[('cat', 14), ('dog', 12), ('eagle', 25), ('pelican', 30), ('squirrel', 6)]
Clearly, this is not what we wanted. To get proper ordering, we need to tell Python that when it considers a tuple for comparison, it should extract the lifespan number. To do so, Python provides us with key
parameter, to which we must pass a function that takes as argument the sequence element under consideration (in this case a tuple) and returns a trasformation of it which Python will use to perform the comparisons - in this case we want the life expectancy at the 1-th position in the
tuple:
[46]:
animals.sort(key=lambda t: t[1])
[47]:
animals
[47]:
[('squirrel', 6), ('dog', 12), ('cat', 14), ('eagle', 25), ('pelican', 30)]
Now we got the ordering we wanted. We could have written the thing as
[48]:
def myf(t):
return t[1]
animals.sort(key=myf)
animals
[48]:
[('squirrel', 6), ('dog', 12), ('cat', 14), ('eagle', 25), ('pelican', 30)]
but lambdas clearly save some keyboard typing
Notice lambdas can take multiple parameters:
[49]:
mymul = lambda x,y: x * y
mymul(2,5)
[49]:
10
Exercise - apply_borders
✪ Write a function apply_borders
which takes a function f
as parameter and a sequence, and RETURN a tuple holding two elements:
first element is obtained by applying
f
to the first element of the sequencesecond element is obtained by appling
f
to the last element of the sequence
Example:
>>> apply_borders(lambda x: x.upper(), ['the', 'river', 'is', 'very', 'long'])
('THE', 'LONG')
>>> apply_borders(lambda x: x[0], ['the', 'river', 'is', 'very', 'long'])
('t', 'l')
[50]:
# write here
[51]:
print(apply_borders(lambda x: x.upper(), ['the', 'river', 'is', 'very', 'long']))
print(apply_borders(lambda x: x[0], ['the', 'river', 'is', 'very', 'long']))
('THE', 'LONG')
('t', 'l')
Exercise - process
✪✪ Write a lambda expression to be passed as first parameter of the function process
defined down here, so that a call to process generates a list as shown here:
>>> f = PUT_YOUR_LAMBDA_FUNCTION
>>> process(f, ['d','b','a','c','e','f'], ['q','s','p','t','r','n'])
['An', 'Bp', 'Cq', 'Dr', 'Es', 'Ft']
NOTE: process
is already defined, you do not need to change it
[52]:
def process(f, lista, listb):
orda = list(sorted(lista))
ordb = list(sorted(listb))
ret = []
for i in range(len(lista)):
ret.append(f(orda[i], ordb[i]))
return ret
# write here the f = lambda ...
[53]:
process(f, ['d','b','a','c','e','f'], ['q','s','p','t','r','n'])
[53]:
['An', 'Bp', 'Cq', 'Dr', 'Es', 'Ft']
Continue
Go on with error handling and testing
[ ]: