## Functions¶

In [1]:
def multiply2(x):
"""Multiply by two"""
return x * 2

def divide(numerator, denominator):
pass

In [2]:
multiply2(4)

Out[2]:
8

A function return alwais something, even if is not specify

In [3]:
divide(1, 0) is None

Out[3]:
True
In [4]:
def func0(x):
return

def func1(x):
return None

def func2(x):
y = x**x

x = 2
print func0(x) == func1(x) == func2(x) == None

True


Define a simple function, again, and use it a parameter of another function!

In [5]:
def add(a, b):
return a + b

def fun(a, b, func):
return func(a, b)


Out[5]:
'cd'

### Exercise 8¶

Write the divide function in order to manage zero division and not numeric values [5 minutes]

In [9]:
def divide(numerator, denominator):
pass


Use the code below as a test:

In [12]:
divide(4, 2)  # return 2

2

In [11]:
divide(4, 0)  # print Divide a number with 0 is not a valid operation!

Divide a number with 0 is not a valid operation!

In [10]:
divide(1,'a') # print Not a valid parameter!

Not a valid parameter!


One possible solution is:

In [ ]:



Use a number variable of parameters.

In [13]:
def summatory(*args):
result = 0
for num in args:
result += num
return result

summatory(1, 2, 3, 4, 5)

Out[13]:
15
In [14]:
summatory(*range(1, 6))

Out[14]:
15
In [15]:
range(1, 6)  # start, stop, step

Out[15]:
[1, 2, 3, 4, 5]

Define a custom function with some default parameters

In [16]:
def multiply(number, mult=2):
return number * mult

In [17]:
multiply(4)

Out[17]:
8
In [18]:
multiply('a')

Out[18]:
'aa'
In [19]:
multiply('a', 6)

Out[19]:
'aaaaaa'
In [20]:
multiply('a', mult=8)

Out[20]:
'aaaaaaaa'

Undefined number of key-value parameter

In [21]:
def contact(**kargs):
for key, val in kargs.items():
print "%s => %r" % (key, val)

In [22]:
contact(pietro=33312388, john=2345678)

pietro => 33312388
john => 2345678


### Exercise 9¶

Define a function that given a number variable of parameters and key-value parameters, print: [5 minutes]

arg: 1
arg: 2
arg: a
arg: [1, 2, 3]
karg[other]: 'ciao'
karg[key]: 'value'
In [25]:
args_kargs(1,2,'a',[1,2,3], key='value', other='ciao')

arg: 1
arg: 2
arg: 'a'
arg: [1, 2, 3]
karg[other]: 'ciao'
karg[key]: 'value'


Solution

In [24]:
def args_kargs(*args, **kargs):
pass


Function containig other functions

In [ ]:
def summ_mult(list_of_tuple):
# define a new function inside
def summ(tupl):
res = 0
for x in tupl:
res += x
return res

result = 1
for el in list_of_tuple:
result *= summ(el)
return result

In [ ]:
summ_mult([(1,2,3), (4,5,6)])


A function could return something using the command return or inside a cycle using yield:

In [ ]:
def use_yield(*args):
for a in args:
yield a * 2

In [ ]:
use_yield(1, 2, 3, 4, 'a')


Using yield transform the function in to a generator, every time that we ask to the generator the next value, the generator compute the next and it will return the resulting object, in this way we don't need to reserve the memory for the list, therefore consume less memory and is generally faster.

In [ ]:
[i for i in use_yield(1, 2, 3, 4, 'a')]

In [ ]:
usy = use_yield(1, 2, 3, 4, 'a')

In [ ]:
usy.next()


Useful functions in the python standard library: http://docs.python.org/2/library/functions.html#built-in-functions

In [ ]:
str(2.8)

In [ ]:
range(1,10,2)

In [ ]:
xrange(1,10,2)

In [ ]:
[i for i in xrange(1,10,2)]

In [ ]:
len([1, 2, 3, 4])  # return the lenght of an object

In [ ]:
names = ['one', 'two', 'three', 'four', 'five']
values = [1, 2, 3, 4, 5]
zip(names, values)  # return a list of pairs

In [ ]:
for name, value in zip(names, values):
print '%5s = %d' % (name, value)

In [ ]:
dir('a')  # return all the attributes of an object


## Decorator¶

Decorator are object that are called to do something before and after a method or a function.

In [ ]:
VERBOSE = True

def verbose(func):
def wrapper(*args):
if VERBOSE:
print "Before to execute: %s" % func.func_name
result = func(*args)
if VERBOSE:
print "After the execution: %s" % func.func_name
return result
return wrapper


We can test our decorator with:

In [ ]:
verbose(multiply2)(1)


Usually decorators are used with "@", and are very useful when you want to avoid to repeat the same operations to different functions.

In [ ]:
VERBOSE = True # try to change

@verbose
def mult(*args):
result = args[0]
for i in args[1:]:
result *= i
return result

mult(1, 2, 3, 4)


Write another decorator that print the execution time of a function.

In [ ]:
import time

def timeit(function):
def timed(*args, **kargs):
# do something before
time_start = time.time()
# execute the function
result = function(*args, **kargs)
# do something after
time_end = time.time()
print 'name=%r (args=%r, kargs=%r) processing time=%10.8f sec' % (function.__name__, args, kargs, time_end - time_start)
return result

return timed

In [ ]:
timeit(multiply2)(2)

In [ ]:
@timeit
def mult(*args):
result = 1
for a in args:
result *= a
return result

In [ ]:
mult(1,2,3,4)