1. What You See is What You Get

A lot of confusion in the first labs was caused by the differences between how humans understand information, how computers (or rather Python) stores it and, more importantly how Python prints it out. Let's look at a simple number.


In [ ]:
print (42)

We can store it in a variable and print it out. It doesn't sound confusing at all!


In [ ]:
x = 42
print (x)

Here, a variable x stores number 42 as an integer. However, we can store the same number as a different type or within another data structure - as float, string, part of a list or a tuple. Depending on the type of variable, Python will print it slightly differently.


In [ ]:
x_float = float(42)
x_scientific = 42e0
x_str = '42'

print ('42 as a float', x_float)
print ('42 as a float in scientific notation', x_scientific)
print ('42 as a string', x_str)

So far it looks pretty normal. float adds floating point to the integer. Scientific notation is typically a float (serious scientists don't work with integers!). 42 as string looks exactly like we expected and similar to the integer, but their behaviors are different. The difference in behavior isn't obvious until we make it a part of a collection, for example a list.


In [ ]:
x_list = [x, x_str, x_float, x_scientific]
print ("All 42 in a list:", x_list)

Here is the thing - Python won't show you quotes when you print a string, but if you print a string in another object, it encloses the string in single quotes. So each time you see this single quotes, you should understand that it's a string and not a number (at least for Python)!

Let's look how:


In [ ]:
x_tuple = tuple(x_list)
x_set = set(x_list)
x_dict = {x_str : x, x_tuple : x_list}

print ("All 42 in a list:", x_list)
print ("All 42 in a tuple:", x_tuple)
print ("All 42 in a set:", x_set)
print ("A dict of 42 in different flavors", x_dict)

Wow, now you should be extreeeeeemely watchful!

  • Look how the shape of brackets differs between a tuple and a list; lists use [brackets], whereas tuples use (parentheses)

  • Look how both sets and dicts use {braces}. That might create some confusion, but each element of set is just an object, whereas in a dictionary you have key : value pair separated by : (colon)

  • Although 42 as integer and 42.0 as floating point are objects of a different kind, for sets they are equal since they have the same value on comparison, exact 42. '42' as a string on the other hand is an object of a different nature, it's not a number. That's why set has only two objects: 42 as integer, and '42' as a string

If you are confused, you can always use type() function to figure out the type of the object you have:


In [ ]:
print ('42 as integer', x, "variable type is", type(x))
print ('42 as a float', x_float, "variable type is", type(x_float))
print ('42 as a float in scientific notation', x_scientific, "variable type is", type(x_scientific))
print ('42 as a string', x_str, "variable type is", type(x_str))

Using type() function might be extremely useful during debugging stage. However, quite often a simple print and a little bit of attention to what's printed is enough to figure out what's going on.

Some objects have different results on calling the print function. For example, let's consider a frozenset, a built-in immutable implementation of python set.


In [ ]:
x_frozenset = frozenset(x_list)
print ("Here's a set of 42:\n", x_set)
print ("Here's a frozenset of 42:\n", x_frozenset)

As you see, when we print set and frozenset, they look very different. Frozenset, as lots of other objects in python, adds its object name when you print it. That makes really hard to confuse set and frozenset!

If you want to do something similar for your custom class, you can do it rather easily in Python. You just need to add a special str method to your custome class which defines the string representation for your object.


In [ ]:
class my_42:
    def __init__(self):
        self.n = 42
    def __str__(self):
        return 'Member of class my_42(' + str(self.n) + ')'
print ('Just 42:',42)
print ('New class:', my_42())

Exercise. Now let's use our knowledge to practise and play with a function that takes a list and returns exactly the same list if every element of the list is a string. Otherwise, it returns a new list with all non-string elements converted to string. To avoid confusion, the function also returns a flag variable showing whether the list has been modified. Try to add some print statements to investigate the types of elements in the list, how the elements are printed out, and how the whole array looks like before and after type conversion.


In [ ]:
def list_converter(l):
    """
    l - input list
    
    Returns a list where all elements have been stringified
    as well a flag to indicate if the list has been modified
    """
    assert (type(l) == list)
    flag = False
    for el in l:
        # print the type of el
        if type(el) != str:
            flag = True
    if flag:
        new_list = []
        for el in l:
            # how would be each element printed out? what's the element type?
            new_list.append(str(el))
            # print how the new list looks like
        return new_list, flag
    else:
        return l, flag

In [ ]:
# `list_converter_test`: Test cell
l = ['4', 8, 15, '16', 23, 42]
l_true = ['4', '8', '15', '16', '23', '42']
new_l, flag = list_converter(l)

print ("list_converter({}) -> {} [True: {}], new list flag is {}".format(l, new_l, l_true, flag))
assert new_l == l_true
assert flag

new_l, flag = list_converter(l_true)
print ("list_converter({}) -> {} [True: {}], new list flag is {}".format(l, new_l, l_true, flag))
assert new_l == l_true
assert not flag