In [ ]:
def say_hello():
print('Hello world!')
In [ ]:
say_hello()
In [ ]:
def max(a, b):
if a > b:
return a
else:
return b
max(1,2)
Arguments changed in functions are local.
In [1]:
def func(a):
a = 2
x = 1
func(x)
print(x)
Arguments changed in functions never are global ... so, we need to return them (if we want to see that changes).
In [2]:
def func(a,b):
a += 1
b -= 1
return a,b
x = 1
y = 2
x, y = func(x, y)
print(x,y)
Containers are passed to functions by reference.
In [4]:
def func(l):
print(l)
del l[0]
l = [1,2]
func(l)
print(l)
In [ ]:
def optional_args(a=1, b=2):
print(a, b)
optional_args()
In [ ]:
optional_args(3)
In [ ]:
optional_args(4,3)
In [ ]:
optional_args(b=3, a=4)
In [ ]:
optional_args(b=3)
In [4]:
# Positional parameters must go before than optinal parameters
def func(a, b=2):
print(a,b)
func(1)
func(1,3)
func(b=2)
In [ ]:
def variable_args(*vargs):
print(vargs)
print(type(vargs))
for i in vargs:
print(i)
print('first argument =', vargs[0])
variable_args("hola", "caracola", ("hi", "folks"))
In [ ]:
def keyworded_args(**kargs):
print(kargs)
print(type(kargs))
for i in kargs:
print(i, kargs[i])
print("'a' argument =", kargs['a'])
keyworded_args(a=1, b='a')
In [ ]:
def add(x, y):
return x + y
def compute(function, x, y):
return function(x, y)
compute(add, 1, 2)
Functions are objects which can, for example, be "copied" to other objects:
In [ ]:
print(type(say_hello))
a = say_hello
a()
In [ ]:
id(say_hello)
In [ ]:
id(a)
In [ ]:
def factorial(x):
if x == 0:
return 1
else:
return x * factorial(x-1)
print(factorial(3))
In [ ]:
import time
now = time.time()
print(factorial(500))
print ("Time =", time.time() - now, "seconds")
In [ ]:
def outter():
def inner():
print('Hello world')
inner()
outter()
Extending the behavior of functions that we don't want (or cannot) to modify:
In [ ]:
def divide(numerator, denominator):
return numerator/denominator
def safe_division(function):
def wrapper(numerator, denominator):
if denominator != 0:
return function(numerator, denominator)
return wrapper
# Function "decoration".
divide = safe_division(divide)
print(divide(1,2))
print(divide(1,0))
In [ ]:
None == 0
The same example using a decorator:
In [ ]:
# This function is identical to the previous one
def safe_division(function):
def wrapper(numerator, denominator):
if denominator != 0:
return function(numerator, denominator)
return wrapper
@safe_division
def divide(numerator, denominator):
return numerator/denominator
print(divide(1,2))
print(divide(1,0))
In [2]:
# http://www.bytemining.com/2010/02/be-careful-searching-python-dictionaries/
import time
def print_timing(func):
def wrapper(*arg):
t1 = time.clock()
res = func(*arg)
t2 = time.clock()
print('%0.3fms' % ((t2-t1)*1000.0))
return res
return wrapper
@print_timing
def my_function():
#stuff
return
my_function()
$\lambda$-funcions are "anonymous" functions:
In [ ]:
# Standard function:
def power(x,y):
return x**y
power(2,3)
In [ ]:
# Lambda function:
power = lambda x,y: x**y
power(2,3)
Lambda functions are useful because they can be defined inline and we don't need give a name to use them:
In [ ]:
(lambda x,y: x**y)(2,3)
In [ ]:
help(filter)
In other words ..., filter()
creates a sequences of elements for which a function returns true.
In [ ]:
for i in filter(lambda x: x < 0, range(-3,3)):
print (i)
In [ ]:
tuple(filter(lambda x: x%2, range(5*2)))
In [ ]:
help(map)
In other words ..., map()
applies a $\lambda$-function to all the items in an input sequence.
In [ ]:
list(map(lambda x: x%2, range(10)))
In [ ]:
from functools import reduce
help(reduce)
In other words ..., reduce()
process iteratively a sequence of items. One of them is item of the sequence and the other, the previus output of the reduce()
.
In [ ]:
n=10; list(range(1, n+1))
In [ ]:
def factorial(n):
return reduce(lambda x,y: x*y, list(range(1, n+1)))
# --------------- ------------------
# function sequence
print(factorial(3))
Only to see the performance:
In [ ]:
import math
print(math.factorial(3))
In [ ]:
now = time.time()
factorial(100000)
print ("Time =", time.time() - now)
In [ ]:
now = time.time()
math.factorial(100000)
print ("Time =", time.time() - now)
Now, lets compute prime numbers. First using a classical approach:
In [ ]:
# https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
import math
N = 20
primes = [True]*N
print('[', end='')
for j in range(2, N):
if primes[j]:
print(j, end=', ')
print(']')
for i in range(2, int(math.sqrt(N))):
if primes[i]:
for j in [i**2+x*i for x in range(N) if i**2+x*i<N]:
primes[j] = False
print('[', end='')
for j in range(2, N):
if primes[j]:
print(j, end=', ')
print(']', i)
# Be aware of this code does not produce a list!
Now using $\lambda$-functions:
In [ ]:
# A different implementation of the Sieve of Eratosthenes
# (http://stackoverflow.com/questions/27990094/finding-primes-with-modulo-in-python)
primes = list(range(2, N))
print(primes)
for i in range(2, int(math.sqrt(N))):
primes = list(filter(lambda x: x == i or x % i, primes))
print(primes, i)
# This code produces a list
Python objects are referenced by names:
In [ ]:
id(1) # The `id()` funtion returns the address of a object
In [ ]:
a = 1
id(a) # "a" is the same object than "1"
Different objects have different id's:
In [ ]:
id(2)
A namespace is a collection of names. All packages, modules, classes and functions define their own namespace (basically, the variables -- or names -- locally defined). The scope of a name is the region of code where that name can be referenced without using any prefix.
(Please, restart this kernel) Do you think that the second print(a)
of in the following code should work?
In [ ]:
def func():
a = 1
print(a)
func()
print(a)
In [ ]:
a = 1
def func():
print(a) # This is the external 'a'
func()
In [ ]:
a = 1
def func():
a = 2
print(a) # This is the internal 'a'
func()
print(a)
However, this is DISCOURAGED. It is confusing and isn’t thread-safe (among other things).
In [ ]:
a = 1
def func():
global a
a = 2 # This is the external 'a'
func()
print(a)
It's better to do:
In [ ]:
a = 1
def func(a):
a = 2
return a
a = func(a)
print(a)