Exceptions and Files Answers

Exception Exercises

Length or None

Write a function len_or_none that returns the length of a given object or None if the object has no length.

>>> len_or_none("hello")
5
>>> len(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
>>> len_or_none(4)
>>> print(len_or_none(4))
None
>>> len_or_none([])
0
>>> len_or_none(zip([1, 2], [3, 4]))
>>> print(len_or_none(zip([1, 2], [3, 4])))
None
>>> len_or_none(range(10))
10

Answers

def len_or_none(var):
    try:
        return len(var)
    except TypeError:
        return None

Average

Make a program average.py that calculates the average of all provided command-line arguments and prints an error message if no arguments are provided and second error message if invalid arguments are provided.

$ python average.py
No numbers to average!
$ python average.py 2 3 4 5 6 7
Average is 4.5
$ python average.py 2 3 4
Average is 3.0
$ python average.py 2 s 3
Invalid values entered, only numbers allowed!

Answers

import sys


def average(nums):
    return sum(nums)/len(nums)


def main(arguments):
    try:
        numbers = [float(num) for num in arguments]
    except ValueError:
        raise SystemExit("Invalid values entered, only numbers allowed!")
    try:
        print("Average is {}".format(average(numbers)))
    except ZeroDivisionError:
        raise SystemExit("No numbers to average!")


if __name__ == "__main__":
    main(sys.argv[1:])

Deep Add

Write a function deep_add that sums up all values given to it, including summing up the values of any contained collections.

>>> deep_add([1, 2, 3, 4])
10
>>> deep_add([(1, 2), [3, {4, 5}]])
15

Answers

With a counter variable:

def deep_add(iterable_or_number):
    total = 0
    try:
        for x in iterable_or_number:
            total += deep_add(x)
    except TypeError:
        total += iterable_or_number
    return total

With sum:

def deep_add(iterable_or_number):
    try:
        numbers = (deep_add(x) for x in iterable_or_number)
    except TypeError:
        return iterable_or_number
    else:
        return sum(numbers)

Deep Flatten

Write a function deep_flatten that flattens all items given to it, including any contained collections, returning a list of all elements. As a bonus, make it work for strings, too.

>>> deep_flatten([1, 2, 3, 4])
[1, 2, 3, 4]
>>> deep_flatten([[1, 2], [3, [4, 5]]])
[1, 2, 3, 4, 5]

Bonus version should work like this:

>>> deep_flatten(["Hello ", "World"])
['Hello ', 'World']
>>> deep_flatten([(1, 2), [3, "bye", {4, 5}]])
[1, 2, 3, 'bye', 4, 5]

Answers

Naive version - works for the given inputs, but will not work for string input, because a single character will not generate the error we are depending upon:

def deep_flatten(iterable_or_item):
    result = []
    try:
        for element in iterable_or_item:
            result.extend(deep_flatten(element))
    except TypeError:
        result.append(iterable_or_item)
    return result

Bonus version that doesn’t break for strings:

def deep_flatten(iterable_or_item):
    result = []
    if isinstance(iterable_or_item, (str, bytes)):
        result.append(iterable_or_item)
    else:
        try:
            for element in iterable_or_item:
                result.extend(deep_flatten(element))
        except TypeError:
            result.append(iterable_or_item)
    return result

Exit Twice

Write a program countdown.py that counts down from a given number, pausing for 1 seconds in between each number. The program should only exit if Ctrl-C is pressed twice.

You can pause for 1 second by using sleep from the time module.

Ctrl-C pressed just once:

$ python countdown.py 5
5
4
^C Press Ctrl-C again to exit
3
2
1

Ctrl-C pressed twice:

$ python countdown.py 10
10
9
^C Press Ctrl-C again to exit
8
7
^C Goodbye!

Answers

import sys
from time import sleep


def countdown(pause):
    interrupted = False
    for i in range(pause, 0, -1):
        print(i)
        try:
            sleep(1)
        except KeyboardInterrupt:
            if interrupted:
                print(" Goodbye!")
                exit(0)
            else:
                interrupted = True
                print(" Press Ctrl-C again to exit")


if __name__ == "__main__":
    countdown(int(sys.argv[1]))

File Exercises

Count

Write a program that accepts a file as an argument and outputs the number of lines, words, and characters in the file.

$ python count.py my_file.txt
Lines: 2
Words: 6
Characters: 28
Longest Line: 17

Bonus: also output the maximum line length

Answers

import sys


MESSAGE_TEMPLATE = "\n".join((
    "Lines: {line_count}",
    "Words: {word_count}",
    "Characters: {char_count}",
    "Longest line {max_line_length}",
))


def print_file_stats(filename):
    with open(filename, mode='rt', encoding='utf-8') as stat_file:
        contents = stat_file.read()
    lines = contents.splitlines()
    message = MESSAGE_TEMPLATE.format(
        line_count=len(lines),
        word_count=len(contents.split()),
        char_count=len(contents),
        max_line_length=max(len(x) for x in lines),
    )
    print(message)


if __name__ == "__main__":
    print_file_stats(sys.argv[1])

Reverse

Write a program that reverses a file character-by-character and outputs the newly reversed text into a new file.

Example:

If my_file.txt contains:

This file
is two lines long

Running:

$ python reverse.py my_file.txt elif_ym.txt

Should make elif_ym.txt contain:

gnol senil owt si
elif sihT

Hint: review some of the interesting ways that slice works.

Answers

import sys


def main(in_filename, out_filename):
    with open(in_filename, mode='rt') as original_file:
        contents = original_file.read()
    with open(out_filename, mode='wt') as reversed_file:
        reversed_file.write(contents[::-1])


if __name__ == "__main__":
    main(*sys.argv[1:])

Concatenate

Write a program concat.py that takes any number of files as command-line arguments and sticks the files together, printing them to standard output.

If an error occurs while reading a file, the file should be skipped and an error should be printed.

Bonus: print the error messages to standard error (not standard output).

Tip

You can print to standard error like this:

>>> import sys
>>> print("this is an error", file=sys.stderr)
this is an error

Example usage of concat.py:

$ python concat.py file1.txt file2.txt file3.txt
This is file 1
[Errno 2] No such file or directory: 'file2.txt'
This is file 3

Answers

import sys

def main(args):
    for filename in args:
        try:
            with open(filename) as concat:
                contents = concat.read()
        except Exception as error:
            print(error, file=sys.stderr)
        else:
            print(contents)

if __name__ == "__main__":
    main(sys.argv[1:])

Sort

Write a program sort.py which takes a file as input and sorts every line in the file (ASCIIbetically). The original file should be overwritten.

Example:

$ python sort.py names.txt

If file names.txt started out as:

John Licea
Freddy Colella
James Stell
Mary Carr
Doris Romito
Janet Allen
Suzanne Blevins
Chris Moczygemba
Shawn McCarty
Jennette Holt

It should end up as:

Chris Moczygemba
Doris Romito
Freddy Colella
James Stell
Janet Allen
Jennette Holt
John Licea
Mary Carr
Shawn McCarty
Suzanne Blevins

Answers

Reading, closing, and re-opening:

import sys

def main(filename):
    with open(filename) as unsorted_file:
        lines = unsorted_file.readlines()
    with open(filename, mode='wt') as sorted_file:
        sorted_file.writelines(sorted(lines))

if __name__ == "__main__":
    main(sys.argv[1])

Reading and writing at once:

import sys

def main(filename):
    with open(filename, mode='r+') as my_file:
        lines = my_file.readlines()
        my_file.seek(0)
        my_file.writelines(sorted(lines))
        my_file.truncate()

if __name__ == "__main__":
    main(sys.argv[1])