Exceptions, File IO, & Modules

CIS1902 Python Programming

Reminders

  • Homework 0 is due tonight!
  • This is the final lecture on Python
    • If you need to brush up, the documentation is always the first place to check!
  • Homework 1 will be released later this week, covering the remaining Python language specifics
    • The focus will be comprehensions, lambdas/generators, and classes/magic methods

Agenda

  1. Modules
  2. Input & Output
  3. Exceptions
  4. Testing
  5. Debugging

Modules

Modules

  • We've seen the use of import <something> a few times, but what does this actually do?
  • As we write larger programs, typically we may want to use functions or classes we wrote before without copying them
    • Also, if we need to change the behavior of these we can do so in one place, whereas if we copied the code we would need to ensure we change it everywhere!
  • Most languages have some sort of import or include keyword which makes it clear that I want to use some external code

Modules

  • Python already has a few modules built in for us to use, these are first-party modules
    • Remember from collections import defaultdict? Other examples are math, random, itertools, functools, etc
  • The basic syntax for including a package is import <package>
  • I can also import only the things I need with the syntax from <package> import <thing1>, <thing2>, ...
  • I can rename things by using the as keyword
    • This is useful if there are conflicting or verbose names
    • import <package> as my_custom_name or from <package> import <thing1> as my_custom_name_1, <thing2>, ...

Modules

We can modulize our own code as well!

  1. # quadratic.py
  2. import math
  3. def solutions(a, b, c):
  4. sol1 = (-b + math.sqrt(b**2 - 4*a*c))/(2*a)
  5. sol2 = (-b - math.sqrt(b**2 - 4*a*c))/(2*a)
  6. return sol1, sol2
  7. def vertex(a, b, c):
  8. return (-b/(2*a), c - b**2 / (4*a))
  1. # I can import the entire file
  2. # and use functions like so
  3. import quadratic
  4. quadratic.solutions(1, -2, 1)
  5. quadratic.vertex(1, -2, 1)
  6. # or import a specific function like
  7. from quadratic import solutions
  8. solutions(1, -2, 1)

Modules

Some things to note about local modules:

  • The path of a module depends on where the python command is invoked from
  • The path of the import depends on the folder structure. Say quadratic.py was in a folder called folder, we would import it as import folder.quadratic
    • Before Python 3.3, you used to need to include an __init__.py file within folder to tell Python it was a module. This is no longer the case, but you may come across this when browsing Python libraries.

Modules

  • Typically, I don't want to write code to do something that someone has solved already, e.g. a framework to make web requests
  • However, in order to run this code, I need to download it
  • Most languages use a package manager to help download external libraries
    • These managers also help in managing the various versions these codebases can have

Modules

  • Python's package manager is called pip (Package Installer for Python)
    • You might have some packages already! Try running pip list in your terminal
  • In almost all cases, if you think there's a package for something, there is! Finding packages you may want is usually something like:
    • Search around describing the behavior of the package
    • Find the GitHub page of the package
    • Follow the install instructions for it
    • In most cases, this is just pip install <package>, but some heavier packages may need other requirements

Input & Output

Input & Output

  • We've seen some basic usage of output with print() and f-strings
  • The magic methods __str__() and __repr__() are similar but have subtle differences
    • __str__() is meant for human readable representations
    • __repr__() is meant to generate representations that can be read by the interpreter

Input & Output

We can format f-strings using a format specification following a :

  1. for i in range(1,8):
  2. print(f'{i:0>3b}')
  • 0 is the padding character
  • > says to left pad (or right-align)
  • 3 says the width is 3
  • b says to output binary
  • More specifics in the Python docs
  1. 001
  2. 010
  3. 011
  4. 100
  5. 101
  6. 110
  7. 111

Input & Output

Reading and writing to files is easy in Python!

  1. with open('data.txt') as f:
  2. # if we want to read the entire file
  3. content = f.read()
  4. # if we want to read the file line by line
  5. lines = f.readlines()
  1. data = [123,5,200,9001]
  2. with open('data.txt', 'w') as f:
  3. for d in data:
  4. f.write(f"{d}\n")

Input & Output

  • Note that when writing, we passed 'w' as a second argument
    • This is the mode, which typically are read 'r', write 'w', and append 'a'. By default, the mode is assumed to be read.
  • Also, note the use of the with block
    • This is a type of scope that makes it easy to do setup and cleanup before and after the block
    • Specifically, the magic methods __enter__() and __exit__() are called before or after the with block
    • Using with allows Python to automatically call close() on the file when we are done with it

Exceptions

Exceptions

  • Our code is never perfect, so we need a way to deal with errors
  • We can preemtively catch errors using a try and except block
    • In Java, this is try and catch
  • We can catch and handle specific errors individually or in groups
    • If error A happens, do B, if error C or D happens, do E, otherwise, if any other error happens, do F
  • We can even inspect the Exception object itself by defining it as a variable using the as keyword

Exceptions

  1. while True:
  2. try:
  3. x = int(input('enter a number: '))
  4. print(f'you entered: {x}')
  5. print(f'100 split into {x} shares is: {100/x}')
  6. break
  7. except ValueError:
  8. print('Not a valid number!')
  9. except ZeroDivisionError:
  10. print("I can't divide by 0!")
  11. except (RuntimeError, TypeError):
  12. print('Something weird happened!')
  13. except Exception as e:
  14. print(f'Something unexpected happened! {type(e)}: {e}')
  15. break

Exceptions

In larger projects, you may find it useful to define custom exceptions

  1. class Error(Exception):
  2. # My base error class
  3. # message: description of error
  4. def __init__(self, message):
  5. self.message = message
  6. class InputError(Error):
  7. # An error class raised for invalid input
  8. pass
  9. raise InputError('Cannot parse the value <bad_input>!')

Testing

Testing

  • We can always manually test our code, but as the complexity of our code grows, it becomes more unwield to test manually.
  • Enter unit tests! We can create frameworks to automate testing, all written in code
  • Python has a built in module for testing called unittest, but many other third party packages exist as well

Testing

  • Unit testing allows us to assert that functions return exactly what we expect them to
  • We can also assert that functions fail exactly the way we expect them to
  • There are even more advanced things we can do, but these two functionalities cover most everyday needs
unittest example (from Python docs)
  1. import unittest
  2. class TestStringMethods(unittest.TestCase):
  3. def test_upper(self):
  4. self.assertEqual('foo'.upper(), 'FOO')
  5. def test_isupper(self):
  6. self.assertTrue('FOO'.isupper())
  7. self.assertFalse('Foo'.isupper())
  8. def test_split(self):
  9. s = 'hello world'
  10. self.assertEqual(s.split(), ['hello', 'world'])
  11. # check that s.split fails when the separator is not a string
  12. with self.assertRaises(TypeError):
  13. s.split(2)
  14. if __name__ == '__main__':
  15. unittest.main()

Testing

  • In industry, good practice is typically the Red-Green-Refactor-Green paradigm
  • Before implementing anything, write your tests! They will fail (red)
  • Now, implement your function and get tests to pass (green)
  • Refactor your code to make it cleaner if needed (refactor)
  • Ensure that you did not change the behavior of your code by running tests again (green)

Debugging

Debugging

Breakpoints
  • A breakpoint is a debugging tool that allows the developer to pause execution at a specific a location in code and inspect state
    • In Java, typically this is something provided by the editor. You may have also seen the tool gdb for C/C++.
  • As of Python 3.7, breakpoints are built-in to Python!

Debugging

Using the debugger
  • Once a breakpoint is reached, an environment similar to an interpreter is launched
    • Think of this as an interpreter that has had all of the code before the breakpoint run, meaning that all variables and functions are accessible
  • Here, we can inspect state, step through code line-by-line, modify variables, and even jump to/skip specific lines of code

Debugging

Using the debugger
  • You can do incredible things with the debugger, but for most purposes it's typically enough to just use the following commands
    • c, continues execution until another breakpoint is reached
    • n, run the next line within the current function
    • s, step into the next line, e.g. first line of a function that was called
    • q, quit the debugger and the program
    • h, help, list commands you can execute or h <command> gives more information about a command

Debugging

  1. def function_to_debug(a, b):
  2. a += 100
  3. b.append("item")
  4. breakpoint()
  5. function_to_debug(1, [])
  1. --Return--
  2. > <ipython-input-3-1695a0ad4d5e>(4)function_to_debug()->None
  3. -> breakpoint()
  4. (Pdb) print(a)
  5. 101
  6. (Pdb) print(b)
  7. ['item']

Debugging

  • Typically, when coding, there are two types of errors: compile time and runtime errors
    • Compile time errors happen when our code is not well formed and our code cannot run
    • Runtime errors happen while our code is running
  • Breakpoints are an amazing tool but they can only ever help us debug runtime errors. However, as you might expect, runtime errors are usually much harder to debug.

Debugging

  • Compile time errors are usually easy to fix in a compiled language like Java or Go
  • However, since Python is interpreted, a lot of these errors bubble up to the runtime scope instead
  • Things that can help prevent errors and enable faster workflows
    • Use a syntax highlighter, this way you can visually see if there is a typo
    • Autocompletion in your editor can also prevent typos
    • "Jump to definition" is very useful, typically the shortcut is something like option-click or ctrl-click
    • Linters can ensure your code is always well formatted

Debugging Lab!

https://www.cis.upenn.edu/~cis1902/201/slides/debugging_lab.py