<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Tips and tricks
Step1:  if clauses and embedded lists
Step2:  dictionary and set comprehensions
Step3:  <a id='exercise_1_1'></a>
Exercise 1.1
Step4:  <a id="string_formatting"></a>
2. String formatting
The built-in str type contains a format method that allows complex variable substitutions and value formatting
Step5:  You can identify the variables to be replaced by
Step6:  <a id='exercise_2_1'></a>
Exercise 2.1
Step7:  You could enhance the readability of this message by formatting x and m to 2 decimal places and r as a percentage
Step8:  Warning
For those familiar with Python 2, be aware that the % operator is still available in Python 3 but " will eventually be removed from the language" so
Step9:  <a id='lambda'></a>
3. Lambda functions (or expression or operator!!??)
Lambda expressions are used to create anonymous functions. The syntax of the a lambda expression is
Step10:  When do you want to use lambda?
In some cases, lambda expressions are good for making your code a bit cleaner. 
When?
When you want to a fairly simple function that is going to be called once.
simple means that it contains only one expression.
It is commonly used together with some functions that take another function as an argument
Step11:  <a id='exercise_3_1'></a>
Exercise 3.1
Step12:  <a id='args'></a>
4. Variable length arguments (AKA *args and **kwargs)
The special syntax, * and ** in function definitions is used to pass a variable number of arguments to a function. So the definition of the function would look like this
Step13:  You can also use the *args and **kwargs syntax in the function calls
Step14:  You can mix fixed arguments and variable length arguments
Step15:  When can/shall we use them?
When we want to build a function with variable arguments length
Step16:  Whenever we inherit a class and override some of the methods of inherited class, we should use *args and **kwargs and pass the received positional and keyword arguments to the superclass method.
This could also apply to methods being "re-used" by other methods (if they are not contained in a class).
Step17:  <a id='logical'></a>
5. Disquisitions on Truth and Falsehood
Boolean operators
These are the Boolean operations, ordered by ascending priority
Step18:  <a id='exercise_5_1'></a>
Exercise 5.1
Step19:  Truth value testing
Then tested the truth value of an object (inside an if, a while, or as a boolean operator), this values are considered false
Step20:  Be careful when handling this. It can lead to weird behaviours
Step21:  Comparisons
The difference between equality and identity
Step22:  In the next example, a and b point to different memory locations, each of them containing the same information. Whereas c, points to the same location as a.
Step23:  When we modify a, we modify the memory location where it is pointing. So, when we modify a, we also modify c. In the other hand, nothing happens to b.
Step24:  <a id='scope'></a>
6. Variable scope and lifetime
We call the part of a program where a variable is accessible its scope, and the duration for which the variable exists its lifetime.
Global and local variables
A variable which is defined in the main body of a file is called a global variable. It will be visible throughout the file, and also inside any file which imports that file. We should restrict their use. Only objects which are intended to be used globally, like functions and classes, should be put in the global namespace.
A variable which is defined inside a function is local to that function. It is accessible from the point at which it is defined until the end of the function, and exists for as long as the function is executing.
Step25:  You should not declare a local variable with the same name as a global one.
In this case, within my_func, global_var is a local variable so, it can't be referenced before its declaration.
Step26:  If what you want to do is modify the global variable, you can use the global keyword at the beginning of the function body.
"You can use" means "you have the possibility", but its a VERY BAD PRACTICE
Step27:  Class and Instance variables
class variables are those attributes of a class that existed in the class definition. Class variables are shared by all instances of a class.
instance variables are set when the class has been already instantiated and are unique to each instance.
Step28:  Not using class and instance variables as detailed above, can lead to weird behaviors, specially with mutable objects
Step29:  <a id='mutable'></a>
7. Mutable vs immutable
An immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created.
Main data types in python
Step30:  Why does mutability matter?
As long as our code works, why should we matter about mutability?
Let's see an example
Step31:  What have we done here? Have we modified the value of x?
The answer is NO
What we have done is
Step32:  Exercise 7.1
Try to improve the performance by avoiding to allocate and drop so many objects
Hint
Step33:  <a id='floating'></a>
8. Floating point arithmetics
As most of the programming languages (Java, C++, Fortran, Matlab, ...), python floats are mapped to IEEE-754 "double precision" format.
In summary, a floating-point number ($f$) is rounded to its nearest binary representation with the expression
Step34:  Order of operations can matter
Step35:  Since $\pi$ can not be exactly represented it is not surprising that $sin(\pi)$ is not $0$
Step36:  Unexpected cancellation due to loss of precision
Step37:  Operating numbers of very different magnitudes
Step38:  Overflow and underflow (reaching the maximum and minimum limits)
Overflow Looking for the nth fibonacci number
Step39:  Starting with version 3.1, when printing, python displays the shortest number that maps to the same floating-point representation.
Step40:  Some hints
Use some error margin if you have to check for equality between two floats.
In Python 3.5, the math.is_close function was introduced, which does exactly that
Step41:  In case you really need correctly-rounded decimal floating point arithmetic, check the built-in decimal library.
Step42:  <a id='decorators'></a>
9. Decorators
Let's see what the official doc says about decorators
Step43:  Remember we are doing
Step44:  It may look complicated (and it is), but don't panic. It's the same as in the previous example.
We would use the decorator like this
Step45:  Let's see what happens if the authentication is not correct
Step46:  And what happens if the authentication is correct
Step47:  Example 9.3
In some cases you may want a decorator that accepts input arguments.
For example, we can implement a decorator to check if the input arguments of a function have the desired type. Resulting in something like this
Step48:  Ok, now you can panic a little bit.
Remember the definition of decorator. What you are doing is this
<ASSISTANT_TASK:>
Python Code:
N_SQUARES = 10
# Don't do this!!!
ugly_list = []
for i in range(N_SQUARES):
    ugly_list.append(i**2)
print('ugly list = {}'.format(ugly_list))
    
# You can do the same in one line
wonderful_list = [ i**2 for i in range(N_SQUARES) ]
print('wonderful list = {}'.format(wonderful_list))
Explanation: Tips and tricks:
Comprehensions (list/dict/set)
String formatting
lambda functions
Variable length arguments (AKA *args and **kwargs)
Disquisitions on Truth and Falsehood
Variable scope and lifetime
Mutable vs immutable
Floating point arithmetic
Decorators
Solutions
<a id=comprehensions></a>
1. Comprehensions
Python provides some "compound data types", such as:
* list = ordered and mutable array of objects, ['abcd', 786 , 2.23, 'john', 70.2]
* tuple = immutable list, ('abcd', 786 , 2.23, 'john', 70.2)
* dictionary = unordered and mutable array of key-value pairs, {'name': 'john', 'code': 6734, 'dept': 'sales'}
* sets = unordered and mutable set of objects, {1, 2., 'a'}
* frozen sets, byte arrays ...
Comprehensions provide a concise way of creating some of them (lists, dictionaries or sets). They do not necessarily improve performance with respect to traditional for loops. 
They reduce the amount of code and make it more readable.
End of explanation
# List comprehensions can contain if clauses after the for clause
even_list = [ i**2 for i in range(N_SQUARES) if i % 2 == 0]
print('even list = {}'.format(even_list))
# List comprehensions can be embedded within one another
IN_LEN = 3
embedded_list = [ [ j**2 for j in range(i, i + IN_LEN) ] for i in range(0, N_SQUARES, IN_LEN)]
print('embedded list = {}'.format(embedded_list))
Explanation: if clauses and embedded lists
End of explanation
# You can use a similar syntax to create dictionaries
fancy_dict = {'square of {}'.format(i): i**2 for i in range(N_SQUARES)}
print('fancy dict = {}'.format(fancy_dict))
# and sets
fancy_set = {i**2 for i in range(N_SQUARES)}
print('fancy set = {}'.format(fancy_set))
Explanation: dictionary and set comprehensions
End of explanation
N = 100
all_divisors_list = []
for i in range(1, N + 1):
    divisors_list = []
    for j in range(1, i + 1):
        if i % j == 0:
            divisors_list.append(j)
    all_divisors_list.append(divisors_list)
print('list of divisors = {}'.format(all_divisors_list))
# %load -r 2:5 solutions/03_02_TipsAndTricks.py
Explanation: <a id='exercise_1_1'></a>
Exercise 1.1: Can you rewrite this into a single line of code?
For the N lower integers, find all their divisors.
End of explanation
from datetime import datetime
an_int = 1
a_float = 0.123456
a_datetime = datetime.now()
a_string = 'foo'
a_list = list('abcd')
print('''This is a string formatting example:
* An integer formatted to a fixed length string filled with leading 0s: {0:06}
* A float formatted as a percentage with 2 decimal positions: {1:0.2%}, or as a float with 4 decimal places: {1:0.4f}
* Extract attributes from an object: the year "{2.year}" and the month "{2.month:02}" in a date
* Align a text filling it with hyphens
    - to the left: 
        {3:-<32}
    - to the right:
        {3:->32}
* Access the values in a list: {4[0]}, {4[2]}
'''.format(an_int, a_float, a_datetime, a_string, a_list))
Explanation: <a id="string_formatting"></a>
2. String formatting
The built-in str type contains a format method that allows complex variable substitutions and value formatting
End of explanation
# an example of arguments referenced by their index
print('Example 1: 1st arg: {0}, 2nd arg: {1}, referencing the indexes'.format('a', 'b'))
# Empty brackets are filled with a list of indexes:
print('Example 2: 1st arg: {}, 2nd arg: {}, without referencing the indexes'.format('a', 'b'))
# If an argument is not referenced in the string, it is ignored
print('Example 3: 1st arg: {0}, 2nd arg: {1}, other arguments are ignored'.format('a', 'b', 'c', 'd', 'e', 'f'))
# You can also use keyword arguments:
print('Example 4: keyword arg "a": {a}, keyword arg "b": {b}, "c" is ignored'.format(a='a', b='b', c='c'))
# You can also mix non-keyword and keyword arguments
print('Example 5: 1st arg: {0}, keyword arg "b": {b}'.format('a', 'c', b='b', d='d'))
Explanation: You can identify the variables to be replaced by:
* the index of the argument in the required arguments
* the name of the argument in the keyword arguments
End of explanation
x = 12.3456789012345678901234567890
m = 0.98765432109876543210987654321
r = 0.05
print('The CI for mu is {} \xb1 {} with a significance level of {}'.format(x, m, r))
Explanation: <a id='exercise_2_1'></a>
Exercise 2.1: use string formatting to enhance the messaging
Imagine we want to print a message informing about the CI of the estimation of a parameter mu in terms of the sample mean (x) and the error margin (m), with a significance level (r).
End of explanation
# %load -r 7 solutions/03_02_TipsAndTricks.py
print('The CI for mu is {:.2f} \xb1 {:.2f} with a significance level of {:.0%}'.format(x, m, r))
Explanation: You could enhance the readability of this message by formatting x and m to 2 decimal places and r as a percentage
End of explanation
moan = ''.join([x*5 for x in 'ARGH!'])
print('In the future this may crash!!\n\n%s' %moan)
Explanation: Warning
For those familiar with Python 2, be aware that the % operator is still available in Python 3 but " will eventually be removed from the language" so:
End of explanation
# This expressions are equivalent
sum_ = lambda x, y: x + y
print('This is the result of the lambda function: {}'.format(sum_(1., 2.)))
def sum__(x, y):
    return x + y
print('And this is the result of the standard defined function: {}'.format(sum__(1., 2.)))
Explanation: <a id='lambda'></a>
3. Lambda functions (or expression or operator!!??)
Lambda expressions are used to create anonymous functions. The syntax of the a lambda expression is:
lambda arguments: expression
This expression yields an unnamed function object. This object behaves like a function object defined with:
def &lt;something&gt;(arguments):
    return expression
End of explanation
# Back to the squares example, using lambda and the map function 
list_of_squares = list(map(lambda x: x**2, range(10))) # not a very good example ... better with comprehensions
print(list_of_squares) 
# Let's try with another one: compute the sum of the squares of all the numbers up to 10
import functools
sum_of_list_of_squares = functools.reduce(lambda x, y: x + y, map(lambda x: x**2, range(10)))
print(sum_of_list_of_squares)
# Let's check if the result is ok
sum_of_list_of_squares == sum(list_of_squares)
Explanation: When do you want to use lambda?
In some cases, lambda expressions are good for making your code a bit cleaner. 
When?
When you want to a fairly simple function that is going to be called once.
simple means that it contains only one expression.
It is commonly used together with some functions that take another function as an argument: map, reduce, filter, ...
End of explanation
# %load -r 10,11 solutions/03_02_TipsAndTricks.py
Explanation: <a id='exercise_3_1'></a>
Exercise 3.1: Use lambda with filter function
Use a lambda function as an argument to the filter function to creat a string with all the vocals (upper and lower case) in "This course is ridiculous. I wish I had not enrolled".
You can type filter? to get some help.
Be aware that the ouput of the filter function is an iterator, so you will have to manipulate it in order to turn it into a string.
End of explanation
# A simple example
def foo(*bar, **barbar):
    print('type of bar: {}'.format(type(bar)))
    print('type of barbar: {}'.format(type(barbar)))
    
foo()
# An example
def sample_function(*args, **kwargs):
    
    print('These are the arguments of my function:')
    
    for i, arg in enumerate(args):        
        print('    Variable non keyword argument {}: {}'.format(i, arg))
    
    for karg, varg in kwargs.items():
        print('    Variable keyword argument: {}:{}'.format(karg, varg))
    
    print('-'*36 + '\n')
sample_function(1, 2, kwarg_1=3, kwarg_2=4)
sample_function(6, 5, 4, 3, 2, 1)
Explanation: <a id='args'></a>
4. Variable length arguments (AKA *args and **kwargs)
The special syntax, * and ** in function definitions is used to pass a variable number of arguments to a function. So the definition of the function would look like this:
def foo(arg_1, arg_2, ..., *args, kwarg_1=kwval_1, kwarg_2=kwval_2, ..., **kwargs)
The single asterisk form (*) is used to pass a non-keyworded, variable-length argument list, and the double asterisk form (**) is used to pass a keyworded, variable-length argument list.
Inside the function definition, *args are tuples and **kwargs are dictionaries, so you can access them as usual.
The names args and kwargs are not mandatory, but they are the most commonly used.
End of explanation
args = range(5)
kwargs = {'kwarg_{}'.format(x): x for x in range(5)}
sample_function(*args, **kwargs)
Explanation: You can also use the *args and **kwargs syntax in the function calls
End of explanation
# We want to force arg1 and arg2
def resample_function(arg1, arg2, *args, **kwargs):
    return sample_function(arg1, arg2, *args, **kwargs)
sample_function()
resample_function(1, 2)
resample_function()
Explanation: You can mix fixed arguments and variable length arguments
End of explanation
def multiplication(*args):
    z = 1
    for arg in args:
        z *= arg
    return z
print(multiplication(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
print(multiplication(0.1, 4, 6.7))
Explanation: When can/shall we use them?
When we want to build a function with variable arguments length
End of explanation
class ValueLogger():
    
    def print_values(self, *values):
    
        print('These are my values:')
        for value in values:
            print('    {}'.format(value))
        self.print_separator()
    
    def print_separator(self):
        print('-'*64)
class AdvancedValueLogger(ValueLogger):
    
    def print_values(self, *values):
        if len(values) == 0:
            print('There are no values')
            self.print_separator()
        else:
            super().print_values(*values)
primitive_logger =  ValueLogger()
primitive_logger.print_values()
primitive_logger.print_values(1, 2, 3, 4, 5)
            
            
advanced_logger = AdvancedValueLogger()
advanced_logger.print_values()
Explanation: Whenever we inherit a class and override some of the methods of inherited class, we should use *args and **kwargs and pass the received positional and keyword arguments to the superclass method.
This could also apply to methods being "re-used" by other methods (if they are not contained in a class).
End of explanation
ex_dict_1 = {'k_1': {'k_1_1': 1, 'k_1_2':2}, 'k_2': 3}
# we can use this to simplify our code
# we can rewrite this
if ex_dict_1.get('k_1'):
    if ex_dict_1['k_1'].get('k_1_1'):
        print(ex_dict_1['k_1']['k_1_1'])
    else:
        print(False)
else:
    print(False)
    
# like this
print(ex_dict_1.get('k_1') and ex_dict_1['k_1'].get('k_1_1') or False)
Explanation: <a id='logical'></a>
5. Disquisitions on Truth and Falsehood
Boolean operators
These are the Boolean operations, ordered by ascending priority:
| Operation | Result | Notes |
|:---:|:---:|:--- |
|x or y|if x is false, then y, else x|This is a short-circuit operator, so it only evaluates the second argument if the first one is false.|
|x and y|if x is false, then x, else y|This is a short-circuit operator, so it only evaluates the second argument if the first one is true.|
|not x|if x is false, then True, else False|(not has a lower priority than non-Boolean operators, so not a == b is interpreted as not (a == b), and a == not b is a syntax error.)|
End of explanation
ex_dict_2 = {'type': 'sum', 'data': [1, 2, 3, 4, 5] }
# This code can be reformatted into a single line
if ex_dict_2['type'] == 'raw':
    res_1 = ex_dict_2['data']
elif ex_dict_2['type'] == 'sum':
    res_1 = sum(ex_dict_2['data'])
else:
    res_1 = None
print(res_1)
# %load -r 14,15 solutions/03_02_TipsAndTricks.py
Explanation: <a id='exercise_5_1'></a>
Exercise 5.1: rewrite in a single line
End of explanation
a = ''
if a:
    res = 'true'
else:
    res = 'false'
print('{!r} has been interpreted as {}'.format(a, res))
Explanation: Truth value testing
Then tested the truth value of an object (inside an if, a while, or as a boolean operator), this values are considered false:
None
False
zero of any numeric type, for example, 0, 0.0, 0j.
any empty sequence, for example, '', (), [].
any empty mapping, for example, {}.
instances of user-defined classes, if the class defines a __bool__() or __len__() method, when that method returns the integer zero or bool value False
All the other ones are considered true.
End of explanation
def sum_function(list_of_values):
    if len(list_of_values) > 0:
        return sum(list_of_values)
def wrap_around_sum(list_of_values):
    s = sum_function(list_of_values)
    if not s:
        print('error!!')
    else:
        print('Here is your result {}'.format(s))
wrap_around_sum([1, 2, 3, 4, 5])
    
wrap_around_sum([-1, -3, 4])
Explanation: Be careful when handling this. It can lead to weird behaviours
End of explanation
a = 0
b = 0.
c = 4 - 4
print('0 and 0. are equal: {}'.format(a == b))
print('0 and 0. are not the same object: {}'.format(a is b))
print('But two different instances of 0, point to the same object: {}'.format(a is c))
class Dummy():
    def __init__(self, val):
        self.val = val
a = Dummy(0)
b = Dummy(0)
c = 0
print('Two instances of the same class are not equal: {}'.format(a == b))
print('or two instances of different classes: {}'.format(a == c))
# Unless we define the __eq__ method
class NotSoDummy(Dummy):
    def __eq__(self, other):
        if other == self.val:
            return True
        else:
            return False
a = NotSoDummy(0)
b = NotSoDummy(0)
c = 0
print('Now the two instances of the same class are tested equal: {}'.format(a == b))
print('even the two of different classes: {}'.format(a == c))
print('But they are not the same object: {}'.format(a is b))
Explanation: Comparisons
The difference between equality and identity
End of explanation
a = [1, 2, 3] 
b = [1, 2, 3]
c = a
print('a and b are equal: {}, but are they the same instance? {}'.format(a == b, a is b))
print('but a and c are both equal: {}, and the same instance: {}'.format(a == c, a is c))
Explanation: In the next example, a and b point to different memory locations, each of them containing the same information. Whereas c, points to the same location as a.
End of explanation
a[0] = 0
print('c = {}'.format(c))
print('b = {}'.format(b))
# This behaviour does not happen with numbers though
a_1 = 1
a_2 = a_1
a_1 = 2
print(a_2)
Explanation: When we modify a, we modify the memory location where it is pointing. So, when we modify a, we also modify c. In the other hand, nothing happens to b.
End of explanation
global_var = 'foo'
print('global outside: {}'.format(global_var))
def my_func():
    local_var = 'bar'
    print('global inside: {}'.format(global_var))
    print('local inside: {}'.format(local_var))
my_func()
print('local outside: {}'.format(local_var))
Explanation: <a id='scope'></a>
6. Variable scope and lifetime
We call the part of a program where a variable is accessible its scope, and the duration for which the variable exists its lifetime.
Global and local variables
A variable which is defined in the main body of a file is called a global variable. It will be visible throughout the file, and also inside any file which imports that file. We should restrict their use. Only objects which are intended to be used globally, like functions and classes, should be put in the global namespace.
A variable which is defined inside a function is local to that function. It is accessible from the point at which it is defined until the end of the function, and exists for as long as the function is executing.
End of explanation
global_var = 'foo'
def my_func():
    print(global_var)
    global_var = 'bar'
my_func()
Explanation: You should not declare a local variable with the same name as a global one.
In this case, within my_func, global_var is a local variable so, it can't be referenced before its declaration.
End of explanation
global_var = 'foo'
def my_func():
    global global_var
    
    print('original global variable value: {}'.format(global_var))
    global_var = 'bar'
    print('new global variable value: {}'.format(global_var))
my_func()
Explanation: If what you want to do is modify the global variable, you can use the global keyword at the beginning of the function body.
"You can use" means "you have the possibility", but its a VERY BAD PRACTICE
End of explanation
class MySampleClass():
    class_var = 'foo'
    class_list = [] # wrong placement of a mutable object
    
    def __init__(self, instance_var):
        self.instance_var = instance_var
        self.instance_list = []
inst_1 = MySampleClass('bar')
inst_2 = MySampleClass('bar bar')
print('Inst 1 - class var value: {}, instance var value: {}'.format(inst_1.class_var, inst_1.instance_var))
print('Inst 2 - class var value: {}, instance var value: {}'.format(inst_2.class_var, inst_2.instance_var))
Explanation: Class and Instance variables
class variables are those attributes of a class that existed in the class definition. Class variables are shared by all instances of a class.
instance variables are set when the class has been already instantiated and are unique to each instance.
End of explanation
inst_1.class_list.append('foo')
inst_1.instance_list.append('foo')
inst_2.class_list.append('bar')
inst_2.instance_list.append('bar')
print('class_list is shared by all instances. inst_1: {}, inst_2: {}'.format(inst_1.class_list, inst_2.class_list))
print('instance_list is not: inst_1: {}, inst_2: {}'.format(inst_1.instance_list, inst_2.instance_list))
Explanation: Not using class and instance variables as detailed above, can lead to weird behaviors, specially with mutable objects
End of explanation
my_list = [1, 2, 3]
my_tuple = (1, 2, 3)
# we can modify a value in a list
my_list[2] = 4
# or we can extend it, drop some value, ...
my_list.append(5)
my_list.remove(1)
print('my_list: {}'.format(my_list))
# We can not do that with tuples
my_tuple[2] = 4
# Tuples don't have any append, remove,... methods
Explanation: <a id='mutable'></a>
7. Mutable vs immutable
An immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created.
Main data types in python:
| Immutable | Mutable |
|:---:|:---:|
|int|list|
|float|dict|
|decimal|set|
|complex|custom class (by default)|
|bool| |
|string| |
|tuple| |
This implies that lists can be modified and tuples can not
End of explanation
# We can do this:
x = 'foo'
print('old value of x: {}'.format(x))
x += ' bar'
print('new value of x: {}'.format(x))
Explanation: Why does mutability matter?
As long as our code works, why should we matter about mutability?
Let's see an example
End of explanation
import csv
import time
with open('../resources/iris.csv', 'r') as f:
    reader = csv.reader(f)
    iris_lines = list(reader)
iris_lines = iris_lines*100 # artificially increase the size of the data
    
print(iris_lines[:10])
# if we want to build a string with the concatenation of all the 'species' we could to this
init = time.clock()
species = iris_lines[1][4]
for iris_line in iris_lines[2:]:
    species += ',' + iris_line[4]
end = time.clock()
comp_time = end - init
print('computation took {:0.8} seconds\n'.format(comp_time))
Explanation: What have we done here? Have we modified the value of x?
The answer is NO
What we have done is:
* create a string with value 'foo' and point x to it
* create a string with value 'foo bar', point x to it and throw away the old one.
In some cases this may add a lot of overhead because we are allocating and throwing lots of large objects.
Let's see an example:
End of explanation
# %load -r 18:25 solutions/03_02_TipsAndTricks.py
Explanation: Exercise 7.1
Try to improve the performance by avoiding to allocate and drop so many objects
Hint: use the str.join function (call ?str.join to see its docstring)
End of explanation
0.1 + 0.1 + 0.1 == 0.3
Explanation: <a id='floating'></a>
8. Floating point arithmetics
As most of the programming languages (Java, C++, Fortran, Matlab, ...), python floats are mapped to IEEE-754 "double precision" format.
In summary, a floating-point number ($f$) is rounded to its nearest binary representation with the expression: 
$$f \simeq s\frac{J}{2^N}$$
Where:
|  | Sign (s) | Exponent (N) | Fraction (J) |
|:---:|:---:|:---:|:---:|
| Number of bits | 1 | 11 | 52 |
You can find more information about floating-point arithmetics in python in the official python documentation and more details on the IEEE-754 standard in Wikipedia and a quite extense paper on the subject
Why do we show this?
This implementation may lead to non-intuitive behaviors:
Some real numbers can not be exactly represented as a float, this may throw some rounding errors
End of explanation
b = 1e-16 + 1 - 1e-16
c = 1e-16 - 1e-16 + 1
b == c
Explanation: Order of operations can matter
End of explanation
from math import sin, pi, sqrt
sin(pi)
Explanation: Since $\pi$ can not be exactly represented it is not surprising that $sin(\pi)$ is not $0$
End of explanation
sqrt(1e-16 + 1) - 1 == 0.
Explanation: Unexpected cancellation due to loss of precision
End of explanation
1e+12 + 1e-5 == 1e+12
Explanation: Operating numbers of very different magnitudes
End of explanation
def fib(n):
    return ((1. + sqrt(5.))**n - (1. - sqrt(5.))**n)/(2**n*sqrt(5.))
print([int(fib(i)) for i in range(1, 20)])
fib(700)
Explanation: Overflow and underflow (reaching the maximum and minimum limits)
Overflow Looking for the nth fibonacci number
End of explanation
# This numbers have all the same binary representation
a = 0.1
b = 0.10000000000000000001
c = 0.1000000000000000055511151231257827021181583404541015625
print(a, b, c)
# However, they are not exactly 0.1
print('{:0.24} {:0.24} {:0.24}'.format(a, b, c))
Explanation: Starting with version 3.1, when printing, python displays the shortest number that maps to the same floating-point representation.
End of explanation
from math import isclose, sin, pi
print(isclose(0.1 + 0.1 + 0.1, 0.3))
print(isclose(sin(pi), 0., abs_tol=1e-09))
Explanation: Some hints
Use some error margin if you have to check for equality between two floats.
In Python 3.5, the math.is_close function was introduced, which does exactly that:
python
def math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0):
End of explanation
from decimal import Decimal
a = Decimal(1)
b = Decimal(10)
c = Decimal(3)
print(a/b, c/b)
a/b + a/b + a/b == c/b
Explanation: In case you really need correctly-rounded decimal floating point arithmetic, check the built-in decimal library.
End of explanation
def p_a_d(some_func):
   def wrapped(*args, **kwargs):
       print("going to run function '{}' with arguments: {}, {}".format(some_func.__name__, args, kwargs))
       res = some_func(*args, **kwargs)
       print("the result of '{}' is: {}".format(some_func.__name__, res))
   
   return wrapped
@p_a_d
def dummy(i):
   return i*10
dummy(1)
Explanation: <a id='decorators'></a>
9. Decorators
Let's see what the official doc says about decorators:
A function returning another function, usually applied as a function transformation using the @wrapper syntax. Common examples for decorators are classmethod() and staticmethod().
The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:
```python
def f(...):
    ...
f = staticmethod(f)
@staticmethod
def f(...):
    ...
```
The same concept exists for classes, but is less commonly used there. See the documentation for function definitions and class definitions for more about decorators.
Example 9.1
Let's see the simplest possible example (not simple though)
End of explanation
auth_tokens = {'user1': 'CLb3MML7GEXoaElk0DFtxuS0uhzYsDOHmdsj',
              'user2': 'uuR4QxFQtwMp5RCVEZTh93lAeLnV1sQF1ZTk'}
def check_authentication(request):
    ''' Check if the token in the request correspond to the one stored'''
    user = request.get('user')
    token = request.get('token')
    
    if auth_tokens.get(user) and auth_tokens[user] == token:
        return True
    else:
        return False
def authenticate(func):
    '''Decorator to add authentication checking'''
    def authenticate_and_call(request):
        if not check_authentication(request): 
            raise Exception('Authentication Failed.')
        return func(request)
    return authenticate_and_call
Explanation: Remember we are doing: dummy = p_a_d(dummy)
So calling: 
python
dummy(1) 
is the same as:
python
wrapped_func = p_a_d(dummy)
wrapped_func(1)
Example 9.2
Let's see a more useful (and complicated) example.
We will implement a simple authentication system.
We want to run the functions in our API only if the user is correctly authenticated. Let's assume that all the functions in our API have a request dictionary as input. The authentication data is stored in the user and token keys of the request. And we need to check the autenticity of this information against the information in our backend before running the function.
We could implement it as follows
End of explanation
@authenticate
def dummy_sum(request):
    return request.get('param1', 0.0) + request.get('param2', 0.0)
Explanation: It may look complicated (and it is), but don't panic. It's the same as in the previous example.
We would use the decorator like this:
python
@authenticate
def some_func(request):
    ...
and call the decorated function: some_func(request)
Keep in mind that what you are doing is this:
```python
def some_func(request):
    ...
some_func = authenticate(some_func)
``
sosome_func(request)is the same asauthenticate(some_func)(request)`
We create a dummy function to test the authentication decorator
End of explanation
dummy_sum({'user': 'user1', 'token': '7jHeWjvt5qAn281eLHbnwKApay2rggAlrbOk', 'param1': 2.0, 'param2': 3.0})
Explanation: Let's see what happens if the authentication is not correct
End of explanation
dummy_sum({'user': 'user1', 'token': 'CLb3MML7GEXoaElk0DFtxuS0uhzYsDOHmdsj', 'param1': 2.0, 'param2': 3.0})
Explanation: And what happens if the authentication is correct
End of explanation
def accepts(*wrapper_args):
    def wrapper(f):
        def check_input_and_call(*func_args):
            for func_arg, wrapper_arg in zip(func_args, wrapper_args):
                if type(func_arg) != wrapper_arg:
                    raise Exception('wrong type for argument {}'.format(func_arg))
            return f(*func_args)
        return check_input_and_call
    return wrapper
Explanation: Example 9.3
In some cases you may want a decorator that accepts input arguments.
For example, we can implement a decorator to check if the input arguments of a function have the desired type. Resulting in something like this:
python
@accepts(type1, type2, ...)
def some_func(arg1, arg2, ...):
    ...
That would be called: some_func(arg1, arg2, ...)
End of explanation
@accepts(float, int)
def compute_root(base, degree):
    return base**(1./float(degree))
compute_root(4., 2.)
compute_root(4., 2)
Explanation: Ok, now you can panic a little bit.
Remember the definition of decorator. What you are doing is this:
some_func = accepts(type1, type2, ...)(some_func)
So some_func(arg1, arg2, ...) is the same as accepts(type1, type2, ...)(some_func)(arg1, arg2, ...)
Pffffffffff
We apply this decorator to a function that calculates the root of a float. We want the degree of the root to be an integer
End of explanation
<END_TASK>