Intermediate Topics in Python

Author: Ahmed Hasan

Made for U of T Coders, to be delivered on 05/04/2017

Contents

  1. Reading/Writing Files
  2. Methods/Attributes
  3. List Comprehensions
  4. Control Flow Tools - pass, continue, break
  5. assert() - if we have time
  6. Try/except - if we have time

What I'm assuming you know

Everything in Madeleine's Intro to Python lesson! I tried to have this lesson pick up right where the intro material left off. This includes:

  • the interpreter
  • variables
  • lists
  • indexing/slicing
  • if statements
  • for loops (and loop syntax in general)
  • functions
  • very basic NumPy, Pandas, matplotlib

Reading (and Writing) Files

Let's say we have 50 students who wrote a test. This test was out of 40 points, and their results - in percentages - are stored in a text file called scores.txt. We want to import these into a Python list.


In [ ]:
# let's make sure of our working directory

import os
os.chdir('/Users/Ahmed/Desktop')

In [ ]:
with open('scores.txt', 'r') as file:
    scores = file.read().split(',')

In [ ]:
print(scores)

The eagle-eyed amongst you may notice the quotes around each number - but we'll address that in a bit.

Methods and Attributes


In [ ]:
len(scores)

Our txt file missed a student! Let's say she got an 89 on the test. How can we add her score to our list?


In [ ]:
# should we do this?

scoresfixed = scores + [89]  

# or should we open the file in Notepad/TextEdit and manually put the 89 in?

In [ ]:
# Python contains built-in methods and attributes for each object type
dir(scores)

In [ ]:
help(scores.append)

Looks like what we want, but let's check on insert and extend just to be sure -


In [ ]:
help(scores.insert)

In [ ]:
help(scores.extend)

In [ ]:
# append it is!

scores.append(89)

print(scores)

It's possible to define a custom set of methods and attributes in order to create new classes of objects. We won't go into much depth about these here, though. (See: Lina's lesson on classes in Python)

List Comprehensions

Let's say we want to get the original scores, out of 40. Is there a built-in method we could use in order to do the same thing to every item in a list?


In [ ]:
dir(mylist)

Apparently not. Perhaps a for loop is the way to go? Let's test that out.


In [ ]:
for i in range(len(scores)):
    scores[i] = scores[i] * 0.4
    
print(scores)

Whoa, that's not right!


In [ ]:
print(scores)

Looks like Python imported the data as a list of strings, save for that one 89% we appended.

We need to find a way to change those strings to integers instead, and then multiply them by 0.4.


In [ ]:
# this is one way?
for num in scores:
    if isinstance(num, str):
        num = int(num)
        num = num * 0.4
    else:
        pass

In [ ]:
# or - we could use a list comprehension!

scores = [int(num)*0.4 for num in scores] 

# conditionals can also be included in list comprehensions - this is valid syntax

scores = [int(num)*0.4 for num in scores if isinstance(num, str)]

In [ ]:
# else clauses in list comprehensions

scores = [int(num)*0.4 if isinstance(num, str) else num*0.4 for num in scores]

For large operations, list comprehensions are also faster than using list.append. When the latter is a part of a loop, Python looks up the list in memory at every single iteration, which can really slow things down for large lists. List comprehensions, on the other hand, do not require the look up operation. They're also far more visually compact while conveying the same amount of information.


In [ ]:
print(scores)

In [ ]:
len(scores)

Notice how the value we appended is gone. This is because we reassigned the list comprehension to scores above. Unlike append, list comprehensions do not alter an object in place - they are an object in their own right. It can sometimes be useful to print out a list comprehension to stdout before assigning it.

For future reference, we could use an assertion to make sure we did things right. We'll talk about that further down.


In [ ]:
# putting it all together - the right way, all along!

with open('scores.txt','r') as f:
     scores = [int(num) for num in f.read().split(',')]

In [ ]:
print(scores)

Control Flow Tools - Pass, Continue, Break

So you'll notice that in that sample loop before the list comprehension, we used a 'pass' statement. Here it is again:


In [ ]:
for num in scores:
    if isinstance(num, str):
        num = int(num)
        num = num * 0.4
    else:
        pass

pass is part of a family of useful operators within Python that allow for more precise control flow operations.


In [ ]:
mylist = [1,2,3,4,5,6,7]

In [ ]:
# pass is a placeholder
for num in mylist:
    if num%2 == 0:
        pass
    print(num)

In [ ]:
# break will exit the loop
for num in mylist:
    if num%2 == 0:
        break
    print(num)

In [ ]:
# continue will immediately jump to the next iteration
for num in mylist:
    if num%2 == 0:
        continue
    print(num)

In [ ]:
mylist.append('hello')
print(mylist)

In [ ]:
# pass is useful to make certain conditions explicit and take care of outside cases
# while it's not 'necessary' here for the loop to function, it makes the operation clearer

for i in range(len(mylist)):
    if isinstance(mylist[i], int):
        mylist[i] = mylist[i] * 0.5
    elif isinstance(mylist[i], str):
        pass
    
print(mylist)

Assertions ('Sanity Checks')

Let's say the following function was in the middle of a long script that runs input data through a series of operations.


In [ ]:
def sumdoubles(x,y):
    '''Multiplies inputs by 2, and returns their sum.'''
    out = x*2 + y*2
    return out

In [ ]:
sumdoubles(2,3)

What happens if something goes wrong, and a pair of strings are somehow given as input?


In [ ]:
sumdoubles('hello', 'how goes?')

That output could be really bad for the downstream parts of our script if they're expecting numerical input!


In [ ]:
# assertions are handy to avoid the propagation of errors

def sumdoubles(x,y):
    assert(not isinstance(x, str) and not isinstance(y, str))
    out = x*2 + y*2
    return out

In [ ]:
sumdoubles(2,3)

In [ ]:
sumdoubles(4,6.5)

In [ ]:
sumdoubles('hey', 'strings are cool')

Assertions do stop a script in its tracks though. What if we want Python to ignore an error and move forward?

try/except


In [ ]:
newlist = [1,2,3,'a',4,5]

In [ ]:
for item in newlist:
    print(item * 0.5)

In [ ]:
# but let's say we just want to ignore the string, instead of ceasing the operation

for item in newlist:
    try:
        print(item * 0.5)
    except TypeError:
        print('ignored', item)