Workshop 18

Files. Exceptions and errors.

Files

Python provides facilities to work with files. The open() function returns a file object, and is most commonly used with two arguments:

open(filename, mode)

File modes:
  • 'r' - if the file will only be read
  • 'w' - for only writing (an existing file with the same name will be erased)
  • 'a' - opens the file for appending; any data written to the file is automatically added to the end.
  • 'r+' or 'w+' - opens the file for both reading and writing.

The mode argument is optional, 'r' is used by default

File atributes:
  • file.closed - returns True if the file is closed
  • file.mode returns file mode.
  • file.name returns file name.
File methods:
  • close() - closes a file. All files must be closed in the end of the program.
  • read() - reads from the cursor positon till the end of file.
  • read(n) - reads the first n bytes from the cursor position and shifts the position forward.
  • readline() - returns a line read from the file.
  • readlines() - returns a list of line read from the file.
  • write() - writes a line to a file and returns the number of written charactes.
  • writelines() - returns a list of lines to a file (does not insert a line separator). Does not return any value.
  • tell() - returns the cursor position.
  • seek(n, [from]) - moves the cursor forward by n bytes:
    • from the beginning of file (from = 0 or not specified),
    • from the cursor position (from = 1),
    • from the end of file (from = 2)
In [ ]:
f = open('my_file.txt', 'w+')
print(f.name, f.closed, f.mode)

f.write('0123456789')
f.seek(5)
f.write('Hello, World!')
f.read()
f.close()

print(f.name, f.closed, f.mode)
my_file.txt False w+
my_file.txt True w+
In [ ]:
f = open('my_file_2.txt', 'w')
print("cursor: %d" % f.tell())

for i in range(10):
    print("cursor: %d" % f.tell())
    f.write('This is line %d\n' % i)

print("cursor: %d" % f.tell())
f.close()
In [ ]:
f = open('my_file_2.txt', 'r')

print("Read all file: ")
f.seek(0, 0)
print(f.read())

print("Read file by lines: ")
f.seek(0, 0)
for line in f:
    print(line)

print("Read all lines from file: ")
f.seek(0, 0)
lines = f.readlines()
print(lines)

f.close()
In [ ]:
f = open('my_file_3.txt', 'w')

for i in range(10):
    print(i, file=f)

f.close()

f = open('my_file_3.txt', 'r')
print(f.read())
In [ ]:
f = open('my_file_3.txt', 'r+')

f.seek(0, 0)
for i in range(10):
    f.write(str(i))
    f.write('\t')

f.seek(0, 0)
data = []
for line in f:
    data.append(list(map(int, line.split())))

f.close()
    
for d in data:
    print(d)

Exceptions and errors

Types of errors:

  • Syntax errors
  • Exceptions

List of Python builtin exceptions: https://docs.python.org/3/library/exceptions.html

Syntax errors:

In [ ]:
# Missing colon ':'
for i in range(10)
    print('HELLO')
  File "<ipython-input-8-b471013ee237>", line 2
    for i in range(10)
                      ^
SyntaxError: invalid syntax
In [ ]:
# Invalid indentation
for i in range(10):
print('HELLO')
  File "<ipython-input-9-be70e05286a3>", line 3
    print('HELLO')
        ^
IndentationError: expected an indented block

Exceptions:

In [ ]:
# Bad operand type or bad operation
x = '10'
y = 2

z  = x / y
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-c72d3a534f78> in <module>()
      3 y = 2
      4 
----> 5 z  = x / y

TypeError: unsupported operand type(s) for /: 'str' and 'int'
In [ ]:
# Division by zero
x = 10
y = 0

z = x // y
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-11-3b027da74086> in <module>()
      3 y = 0
      4 
----> 5 z = x // y

ZeroDivisionError: integer division or modulo by zero
In [ ]:
# List index out of bounds
x = [1,2,3]
print(x[3])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-3-347df076b4ce> in <module>
      1 x = [1,2,3]
----> 2 print(x[3])

IndexError: list index out of range
In [ ]:
# Modifying a tuple
y = (1, 2, 3)
y[0] = 10
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-4c5a1127b831> in <module>
      1 y = (1, 2, 3)
----> 2 y[0] = 10

TypeError: 'tuple' object does not support item assignment
In [ ]:
# Undefined variable
zz = xx + yy  
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-1be4c0af26ab> in <module>
      1 # Undefined variable
----> 2 zz = xx + yy

NameError: name 'xx' is not defined
In [ ]:
# Invlid value format
x = int('ABS') # Not a number
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-1c3e50c8a66d> in <module>
      1 # Invlid value format
----> 2 x = int('ABS') # Not a number

ValueError: invalid literal for int() with base 10: 'ABS'

Exception handling

Exception handling facilities of Python:

  • try/except - catch and recover from exceptions raised
  • try/finally - perform cleanup actions, whether exceptions occur or not
  • raise - trigger an exception manually in your code
  • assert - conditionally trigger an exception in your code
  • with/as - implement context managers
Handling exceptions:
In [ ]:
x = 10
y = 0

try:
    z = x // y
except ZeroDivisionError:
    print("Please do not divide by zero!")
Please do not divide by zero!
In [ ]:
while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("It was not a number. Please try again...")
Please enter a number: фыа
It was not a number. Please try again...
Please enter a number: 13
In [ ]:
def input_int():
    try:
        return int(input())
    except:
        return -1

for i in range(5):
    print("x = %d" % input_int())
x = 5
x = -1
x = 54
x = -1
x = -1
In [ ]:
print("Before try")
try:
    print("  Before exception")
    x = int(input())
    y = 10 / x
    print("  After exception")
except ValueError:
    print("  In except ValueError")
except ZeroDivisionError:
    print("  In except ZeroDivisionError")
else:
    print("  In else")
finally:
    print("  In finally")
print("After try") 
    
Before try
  Before exception
asf
  In except ValueError
  In finally
After try
Cleanup actions
In [ ]:
f = open('my_file_4.txt', 'w')

try:
    for i in range(10):
        print("Writing line %d" % i) 
        f.write('Line %d' % i)
        f.write('\n')
        if i == 8:
            raise Exception("i == 8")
finally:
    print("File will be closed no matter what happens!")
    f.close()
Writing line 0
Writing line 1
Writing line 2
Writing line 3
Writing line 4
Writing line 5
Writing line 6
Writing line 7
Writing line 8
File will be closed no matter what happens!
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-3-7cfa2a3b91a9> in <module>()
      7         f.write('\n')
      8         if i == 8:
----> 9             raise Exception("i == 8")
     10 finally:
     11     print("File will be closed no matter what happens!")

Exception: i == 8
In [ ]:
with open('my_file_5.txt', 'w') as f:
    for i in range(10):
        print("Writing line %d" % i) 
        f.write('Line %d' % i)
        f.write('\n')
        if i == 8:
            raise Exception("i == 8")
Raising exceptions
In [ ]:
def func(a, b):
    if not (0 <= a <= 100):
        raise Exception("a = %d" % a)
    if not (0 <= b <= 100):
        raise Exception("b = %d" % b)
    return a * b

a = int(input())
b = int(input())

try:
    print(func(a, b))
except Exception as e:
    print("Error: %s" % e)
200
1
Error: a = 200
In [ ]:
##### Assertions
In [ ]:
def add_two_numbers(a, b):
    assert a > 0, "a must be positive"
    assert b > 0, "b must be positive"
    return a + b

x = add_two_numbers(1, 2)
assert x == 3 
print(x)

x = add_two_numbers(1, -2)
assert x == 3 
print(x)
3
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-3-6ac36b375f18> in <module>
      8 print(x)
      9 
---> 10 x = add_two_numbers(1, -2)
     11 assert x == 3
     12 print(x)

<ipython-input-3-6ac36b375f18> in add_two_numbers(a, b)
      1 def add_two_numbers(a, b):
      2     assert a > 0, "a must be positive"
----> 3     assert b > 0, "b must be positive"
      4     return a + b
      5 

AssertionError: b must be positive

Program debugging

Do it in PyCharm. Put breakpoints into this code and explore different paths in the control flow.

In [ ]:
def fact(n):
    if n == 1:
        return n
    else:
        return n * fact(n-1)


def func(x, y):
    if x < 0 or y < 0:
        raise ValueError
    return fact(y) / x


x = int(input())
y = int(input())

try:
    if x > y:
        v = func(x, y)
    else:
        v = func(y, x)
    print("Result: ", v)
except:
    print("Something went wrong")

Tasks

Task 1

You are given a list with 10 prime numbers, already in your code. Write a program that asks the user for an index i and outputs the i-th prime number.

Using the example below, write similar code that allows you to enter anything into the input field without crashing the program.

Simpler version: if the input is incorrect, output an error message and end the program.

Complex version: ask the user to enter the correct number, similar to the example below.

If the number is correct, you quit.
If the number is incorrect, you ask the user if they want to continue.
If they want to continue, you repeat the program.
If they don't want to continue, you quit.

In [ ]:
a_list = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]


input_index = int(input())
# ValueError = input is of incorrect data type


print(a_list[input_index])
# IndexError = list index out or range
In [ ]:
with open('data.txt', 'w') as datafile:
    print('1 2 3 4 5', file=datafile)

cont_flag = True

while cont_flag:

    filename = input('Please input file name: ')
    try:
        with open(filename) as datafile:
            print(datafile.read())
        cont_flag = False
    except FileNotFoundError as error:
        print(error)
        print('File does not exist')

    if cont_flag:
        cont_text = input('Enter Y to try again ')
        print(cont_text)
        if cont_text != 'Y' and cont_text != 'y':
            cont_flag = False
Please input file name: data.txt
1 2 3 4 5

Task 2

Write a program that saves a string "Example data" to a file and then reads it back. Remember to close the file before you open it again. Use the with statement to do it.

Pickle

In [ ]:
print(cont_flag)
False

pickle is a Python module used for serialization, or storing a Python object structure as a binary file (or other byte stream).

It is useful in various applications, including saving the state of your work so you can return to it in the future.

Short Summary

If you want to save some object, use pickle.dump(object, file)

If you want to load your saved file, use pickle.load(file)

If you want to save an object you defined on your own (like a function or a class), you have to import it before loading.

You can't save everything, but this should be enough for simple applications.

In [ ]:
import pickle

# An arbitrary collection of objects supported by pickle.
data = {
    'a': [1, 2.0, 3, 4+6j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

with open('data.pickle', 'wb') as f:
    # Pickle the 'data' dictionary using the highest protocol available.
    pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
In [ ]:
import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)
print(data)
{'a': [1, 2.0, 3, (4+6j)], 'b': ('character string', b'byte string'), 'c': {None, True, False}}

Task 3

Write the following program.

The user inputs integer numbers into the program. The numbers are saved into a list. If the user inputs 0, it is a sign that the input has ended. The number 0 itself shouldn't be included into the list. The list is empty at the start.

The program should be persistent: once the user runs it again, it should load all the previously inputted numbers and append the new ones at the end of the list.

To achieve this, pickle the list at the end and unpickle it at the beginning of the program. When you run the program for the first time, the pickled file from "the last time" wouldn't exist. Write an exception handler to output a notice message "New file" in this case, then append the numbers to an empty list and pickle the list.

In [ ]:
import pickle
data = []