0% found this document useful (0 votes)
6 views17 pages

06_functions.ipynb - Colab

The document explains the concept of functions in programming, detailing how to define and call them, as well as the use of parameters and return values. It covers local and global variable scopes, default parameter values, and the importance of documentation through docstrings. Additionally, it introduces recursive functions and provides examples for better understanding.

Uploaded by

899141
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
6 views17 pages

06_functions.ipynb - Colab

The document explains the concept of functions in programming, detailing how to define and call them, as well as the use of parameters and return values. It covers local and global variable scopes, default parameter values, and the importance of documentation through docstrings. Additionally, it introduces recursive functions and provides examples for better understanding.

Uploaded by

899141
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 17

 Functions

"Programming" can be seen as the process of breaking a large and complex computational
task into smaller and smaller subtasks, until the subtasks are simple enough to be performed
by combining basic instructions. To improve code readability and promote code reuse, it’s
convenient to associate a name with these subtasks, which becomes callable functions.

In the context of programming, a function is thus a named group of statements that performs
a computation. Each function must be before “de6ned” and then “called” by its name.
Optionally, we can specify parameters to supply so as to call a function.

We already used a lot of pre-de6ned functions, by calling them with suitable arguments:

int('32') # transform string into integer


type(1.3) # return the type of the parameter
len("Hello") # return the length of a string
str(25) # transform the parameter into string

We also used composition of functions, where the return of a function is used in turn as a
argument of another, and so on:

input_num = "44"
print(str(int(input_num) + 1))

Since expressions returns objects, these expressions can be used as function arguments (see
the expression: int(input_num) + 1 used as a parameter of str() ).

#v = int(input("give me a number > "))


#print(v)

input_num = "44"
print(str(int(input_num) + 2))

# the use of str() is not compulsory in print, since all arguments are automaticall
print(int(input_num) + 2)

46
46
 DeIning and calling a function
To de6ne and then call/invoke a function, you have to use the following syntax:

def name_of_function([<list of comma-separated parameters>]): # the list can be em


< indented block of statements >

....

name_of_function([<list of comma-separated arguments>]) # function call

Therefore, if function arguments are needed, when you deIne the function you have to
introduce one or more variables called parameters between a pair of parentheses. For
example:

def print_twice(param1, param2):


print(param1, param2)
print(param1, param2)

This function assigns the arguments, passed at calling time, to the two parameters param1
and param1 . Indeed, when the function print_twice is called, it prints the value of the two
parameters twice. For example, after the deInition, you can call the function as follows:

print_twice("Thanks", 1000)

This function works with any argument that can be printed by the build-in function print .

You can also pass expressions as arguments, which are Irst evaluated before being assigned
to the parameter variables. For example, look at the code below, where we also deIned a
function without parameters:
def print_twice(param1, param2):
print(param1, param2)
print(param1, param2)

def print_my_strings():
msg = "Hello!"
print(msg)
print("How are you from 0 to 10?")

#print_twice("Thanks") # run-time error


print_twice('Thanks', 1000)
print()
print_my_strings() # no arguments must be passed in this case

Thanks 1000
Thanks 1000

Hello!
How are you from 0 to 10?
# There are so-called "magic methods" in Jupyther notebook
# For example, %who shows the list of all variables defined within the current note
n = 10

%who

print_twice("aa", "bb")
print(n)

# This is a Cheat Sheet of magic methods


# https://www.kdnuggets.com/wp-content/uploads/Jupyter_Notebook_Magic_Methods_Cheat

%lsmagic

n print_my_strings print_twice
aa bb
aa bb
10
Available line magics:
%alias %alias_magic %autoawait %autocall %automagic %autosave
%bookmark %cat %cd %clear %colors %conda %config %connect_info %cp
%debug %dhist %dirs %doctest_mode %ed %edit %env %gui %hist
%history %killbgscripts %ldir %less %lf %lk %ll %load %load_ext
%loadpy %logoff %logon %logstart %logstate %logstop %ls %lsmagic
%lx %macro %magic %man %matplotlib %mkdir %more %mv %notebook
%page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %pip %popd
%pprint %precision %prun %psearch %psource %pushd %pwd %pycat
%pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun
%reset %reset_selective %rm %rmdir %run %save %sc %set_env %shell
%store %sx %system %tb %tensorflow_version %time %timeit %unalias
%unload_ext %who %who_ls %whos %xdel %xmode
Functions and Flow of control/execution
Recall that program execution always begins at the Irst statement of the program.
Statements are run one at a time, in order from top to bottom.

Function de6nitions do not alter the Now of execution of the program, and the statements
inside the function deInition don't run until the function is called.

A function call is like a detour in the ?ow of execution. Instead of going to the next statement,
the ?ow jumps to the body of the function, and runs the statements there, and then comes
back to resume from where it left off.

This execution scheme works well even if the called function calls another, and so on. Python
is good at keeping track of where the Now of execution is, moving forward and backward
between different functions.

In summary, when you read a program, you don't always want to read from top to bottom.
Sometimes it makes more sense if you follow the Now of execution. Use Python tutor to
visualize the execution of a program with function deInitions and function calls.

 Variables and parameters within functions are local


When you create a variable inside a function deInition, it is local, and its lifetime starts when
the function is called and ends when the execution of the called function ends.

Parameters are also local. For example, outside print_twice , there is no known variables
param1 or param2 . See the example below, and discuss because they generate run-time
errors.
def foo(val):
incr = 12
a = 10
val = val + incr + a + 10
print("local variable:", val)

a = 20
incr = 1000

foo(8)

print(a) # the variable is **not** the one defined in the body of foo()
print(incr) # the variable is **not** defined in this environment
# print(val)

# try to create a variable incr and val outside the function


# run by using pythontutor to visualize the execution

local variable: 40
20

 Scope of variables and functions


Functions can access variables in two different scopes: global and local.

The local namespace is created when the function is called and immediately populated by the
function's arguments. After the function is Inished, the local namespace is destroyed (with
some exceptions).

A variable deIned in the global namespace (deIned outside, and before the function call), can
be read and used from within the function.

In the following example a is in global namespece, while c is in the local namespace of the
function.
def foo1():
c = a + 1 # access varial a, located in the global space
# a = c + 1 # in general, you cannot change variables in the global namespaces
print("local variable: ", c)

a = 15
foo1()
print(a)

local variable: 16
15

%who

a foo foo1 n print_my_strings print_twice

When the name of variable, already deIned in the global space, is reused within the function
to assign (create) a new variable, this new variable in local namespace is distinct from the
variable in the global namaspace.

In the following example the same name a is used in the local and global namespace. Within
the function, we access to the variable in the local namespace, whereas the variable in the
global namespace is left unmodiIed.

def b():
a = c + 1
print('local namespace', a)

a = 12
c = 13
b()
print('global namespace', a)
def b():
#global a
a = c + 1
print('global namespace', c)
print('local namespace', a)
#print('global namespace', a)

a = 12
c = 13
b()
print('global namespace', a)

global namespace 13
local namespace 14
global namespace 12

 Default values of parameter


When you deIne a function you can specify a default value for one or more arguments. This
creates a function that can be called with fewer arguments.

def print_twice(param1, param2 = 'Salvatore!', param3 = 'How are you?'):


print(param1, param2, param3)
print(param1, param2, param3)

print_twice('Hello')
print_twice('Ciao')
print_twice('Hello', param2="Giovanni!", param3 = 'How old are you?')
print_twice('Hello', param2="Giovanni!")
print_twice('Ciao', param3="Come stai?")

Hello Salvatore! How are you?


Hello Salvatore! How are you?
Ciao Salvatore! How are you?
Ciao Salvatore! How are you?
Hello Giovanni! How old are you?
Hello Giovanni! How old are you?
Hello Giovanni! How are you?
Hello Giovanni! How are you?
Ciao Salvatore! Come stai?
Ciao Salvatore! Come stai?
 Docstrings
To document a function, a docstring should be used.

It is a text put at the beginning of a function to explains the interface (doc is short for
documentation). Here is an example:

def fib(n):
'''
Print a Fibonacci series up to n, n>=0.
example for n=35: 0,1,1,2,3,5,8,13,21,34

Args:
n: limit of the value to include in the series, n>=0.
Returns:
No return value, but print the list.
'''
if n == 0:
print(0) # first Fibonacci value
elif n >= 1:
print(0, 1, 1, end=' ') # first three Fibonacci values
next = 2 # new Fib value (to print)
previous = 1 # previous Fib value (already printed)
while next <= n:
print(next, end=' ')
new_next = next + previous # store in "new_next" the next Fib value (to

previous = next # assign to "previous" the penultimate Fib value


next = new_next # assign to "next" the next value to print

print()

By convention, all docstrings are triple-quoted strings, also known as multiline string because
the triple quotes allow the string to span more than one line.

Docstring are treated by Python as multiline comments.

There are tools which use docstrings to automatically produce online or printed
documentation, or to let the user interactively browse through code.
def fib(n):
'''
Print a Fibonacci series up to n, n>=0.
example for n=35: 0,1,1,2,3,5,8,13,21,34

Args:
n: limit of the value to include in the series (n >= 0).
Returns:
No return value, but print the list.
'''
if n == 0:
print(0) # first Fibonacci value
elif n >= 1:
print(0, 1, 1, end=' ') # first three Fibonacci values
next = 2 # new Fib value (to print)
previous = 1 # previous Fib value (already printed)
while next <= n:
print(next, end=' ')
new_next = next + previous # store in "new_next" the next Fib value (to print

previous = next # assign to "previous" the penultimate Fib value


next = new_next # assign to "next" the next value to print
print()

fib(300) # call the function

0 1 1 2 3 5 8 13 21 34 55 89 144 233

# fib
fib(1000)

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987


 Fruitful functions
Many of the Python functions we have used produce return values.

But the above functions are all void: they have an effect, as they print values, but they do not
have a return value. How can we write fruitful functions?

Look at this example, a function that returns the area of a circle with the given radius:

def area(radius):
a = 3.14 * radius**2
return a

We can use the return statement with or without arguments. If the function is void, the
return statement forces the exit from function. If the function is fruitful, the return
statement must be followed by an expression, as in the example above.

You can use fruitful functions within expressions, or assign the function, indeed the returned
value, to a variable.

import math

def area_square(side):
a = side**2
return a

def area_circle(radius):
a = math.pi * area_square(radius) #radius**2
return a

total = area_circle(10) + area_square(3) # usage of fruitful functions within expre


print(area_circle(10))
print(total)

314.1592653589793
323.1592653589793

 Advanced topic: recursive functions


A recursive de6nition is similar to a circular deInition, in the sense that the deInition
contains a reference to the thing being deIned.

It is very common in mathematics. For example, the factorial 𝑛! = 1 ⋅ 2 ⋅ … ⋅ 𝑛 can be


expressed as follows:
0! = 1
𝑛! = 𝑛 ⋅ (𝑛 − 1)!
This deInition says that the factorial of 0 is 1 , and the factorial of any other value, 𝑛 , is 𝑛
multiplied by the factorial of 𝑛 − 1.
So 3! is 3 times 2! , which is 2 times 1! , which is 1 times 0! . 0! is the base case, where the
deInition is known.

Putting it all together, 3! is equal to 3 ⋅ 2 ⋅ 1 = 6.


If you can write a recursive deInition of something, you can write a Python program to
evaluate it.

The Irst step is to decide what the parameters should be. In this case it should be clear that
factorial takes an integer:

def factorial(n):

If the argument happens to be 0, all we have to do is to return 1, otherwise we have to make a


recursive call to Ind the factorial of 𝑛 − 1 and then multiply it by 𝑛 :

def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result

Since 3 is not 0 , we take the second branch and calculate the factorial of 𝑛 − 1 (which is 2 )...
Since 2 is not 0 , we take the second branch and calculate the factorial of 𝑛 − 1 (which is 1
)...
Since 1 is not 0 , we take the second branch and calculate the factorial of 𝑛 − 1 (which is
0 )...
Since 0 equals 0 , we take the Irst branch and return 1 without making any more
recursive calls.
The return value, 1 , is multiplied by 𝑛 , which is 1 , and the result is returned.
The return value, 1 , is multiplied by 𝑛 , which is 2 , and the result is returned.
The return value (2 ) is multiplied by 𝑛 , which is 3 , and the result, 6 , becomes the return value
of the function call that started the whole process.
Try with Python Tutor by deIning and then calling the function:

result = factorial(3)

def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result

print(factorial(4))

24

Advanced topic: Stack diagrams


To keep track of which variables can be used where, it is sometimes useful to draw a stack
diagram, which shows the value of each variable but they also show the function each
variable belongs to.

Each function is represented by a frame, indeed a box with the name of a function beside it
and the parameters and variables of the function inside it.

The frames are arranged in a stack that indicates which function called which, and so on.

The following is the stack diagram of the call of

result = factorial(3)

Stack diagram image

The return values are shown being passed back up the stack. In each frame, the return value
is the value of result , which is the product of 𝑛 and recurse .

In the last frame, the local variables recurse and result do not exist, because the branch
that creates them does not run.

Note how many different instances of variable result are generated during the recursive
call.
 Exercises
1. Write a function check_prime that takes an integer and returns True if the number is
prime, and False otherwise.

2. Write a recursive function that realizes Ibonacci, deIned recursively as follows:


𝑓𝑖𝑏(0) = 0
𝑓𝑖𝑏(1) = 1
𝑓𝑖𝑏(𝑛) = 𝑓𝑖𝑏(𝑛 − 1) + 𝑓𝑖𝑏(𝑛 − 2)
3. Write a recursive function that computes the sum of all integer values 𝑥 in the interval:
from ≤ 𝑥 ≤ to:
𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙, 𝑙) = 𝑙
𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙, 𝑢) = 𝑙 + 𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙 + 1, 𝑢)

# Write a function check_prime(n) that takes an integer and


# returns True if the number is prime, and False otherwise.

def check_prime(n):
'''
Return True if the input argument n is prime, False otherwise
Args:
n: integer to be checked.
Returns:
Boolean value.
'''
is_prime = True

for div in range(2,n):


if n % div == 0: # if n is divisible by div
is_prime = False

return is_prime

print(10, ":", check_prime(10))


print(1, ":", check_prime(1))
print(13, ":", check_prime(13))

10 : False
1 : True
13 : True
# 𝑓𝑖𝑏(0) = 0
# 𝑓𝑖𝑏(1) = 1
# 𝑓𝑖𝑏(𝑛) = 𝑓𝑖𝑏(𝑛−1)+𝑓𝑖𝑏(𝑛−2)

# recursive
def recfib(n):
'''
Print the n-th element of the Fibonacci series (counting from 0)

Args:
n: n-th position in the Fibonacci series.
Returns:
Value of the item in the series at position n.
'''
if n == 0:
return 0
if n == 1:
return 1
return recfib(n-2) + recfib(n-1)

for i in range(20):
print(recfib(i), end=' ')

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

# 𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙,𝑙) = 𝑙
# 𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙,𝑢) = 𝑙 + 𝑠𝑢𝑚_𝑙𝑖𝑠𝑡(𝑙+1,𝑢)

def sum_list(l,u):
'''
Compute and return the sum of the integers x, where l <= x <= u.

Args:
l: lower bound of the series
u: upper bound of the series
requirement: l <= u
Returns:
Sum of the subseries.
'''
if l > u:
s = 0
elif l == u:
s = l
else:
s = l + sum_list(l+1,u)
return s

def sum_list_norec(l,u):
if l > u:
return 0
s = 0
for item in range(l,u+1):
print("-->", item)
s = s + item
return s

print(sum_list(6,5))
print(sum_list(5,5))
print(sum_list(5,10), 10*11/2 - 4*5/2)

print("\n", 6, 5, "*****")
print(sum_list_norec(6,5))

print("\n", 5, 5, "*****")
print(sum_list_norec(5,5))

print("\n", 5, 10, "*****")


print(sum_list_norec(5,10), 10*11/2 - 4*5/2)

0
5
45 45.0

6 5 *****
0

5 5 *****
--> 5
5

5 10 *****
--> 5
--> 6
--> 7
--> 8
--> 9
--> 10
45 45.0

Inizia a programmare o genera codice con l'IA.

You might also like