Using modules and packages with the Python interactive interpreter

As well as calling modules and packages from a Python script, it is often useful to call them directly from the Python interactive interpreter. This is a great way of employing the rapid application development (RAD) technique for Python programming: you make a change of some sort to a Python module or package and immediately see the results of your change by calling that module or package from the Python interactive interpreter.

There are, however, a few limitations and issues to be aware of. Let's take a closer look at how you can use the interactive interpreter to speed up your development of modules and packages; we'll also see where a different approach might suit you better.

Start by creating a new Python module named stringutils.py, and enter the following code into this file:

import re

def extract_numbers(s):
    pattern = r'[+-]?d+(?:.d+)?'
    numbers = []
    for match in re.finditer(pattern, s):
        number = s[match.start:match.end+1]
        numbers.append(number)
    return numbers

This module represents our first attempt at writing a function to extract all the numbers from a string. Note that it is not working yet—the extract_numbers() function will crash if you try to use it. It's also not particularly efficient (a much easier approach would be to use the re.findall() function). But we're using this code deliberately to show how you can apply rapid application development techniques to your Python modules, so bear with us.

This function uses the re (regular expression) module to find the parts of the string that match the given expression pattern. The complicated pattern string is used to match a number, including an optional + or - at the front, any number of digits, and an optional fractional part at the end.

Using the re.finditer() function, we find the parts of the string that match our regular expression pattern. We then extract each matching part of the string and append the results to the numbers list, which we then return back to the caller.

So much for what our function is supposed to do. Let's test it out.

Open a terminal or command-line window, and use the cd command to switch to the directory holding the stringutils.py module. Then, type python to start up the Python interactive interpreter. When the Python command prompt appears, try entering the following:

>>> import stringutils
>>> print(stringutils.extract_numbers("Tes1t 123.543 -10.6 5"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./stringutils.py", line 7, in extract_numbers
    number = s[match.start:match.end+1]
TypeError: unsupported operand type(s) for +: 'builtin_function_or_method' and 'int'

As you can see, our module doesn't work yet—we have a bug in it. Looking closer, we can see that the problem is on line 7 of our stringutils.py module:

        number = s[match.start:match.end+1]

The error message suggests that you are trying to add a built-in function (in this case, match.end) to a number (1), which of course doesn't work. The match.start and match.end values were supposed to be the indices into the string for the start and end of the number, but a quick look at the documentation for the re module shows that match.start and match.end are functions, not simple numbers, and so we need to call these functions to get the values we want. Doing this is easy; simply edit line 7 of your file to look like the following:

        number = s[match.start():match.end()+1]

Now that we've changed our module, let's take a look at what happens. We'll start by re-executing the print() statement to see if that works:

>>> print(stringutils.extract_numbers("Tes1t 123.543 -10.6 5"))

Tip

Did you know that you can press the up arrow and down arrow keys on your keyboard to move through the history of commands that you've typed previously into the Python interactive interpreter? This saves you from having to retype a command; simply use the arrow keys to select the command you want, and press Return to execute it.

You'll immediately see the same error message you saw previously—nothing has changed. This is because you imported the module into the Python interpreter; once a module or package has been imported, it is held in memory and the source file(s) on disk are ignored.

To have your changes take effect, you need to reload the module. To do this, type the following into your Python interpreter:

import importlib
importlib.reload(stringutils)

Tip

If you are using Python 2.x, you can't use the importlib module. Instead, simply type reload(stringutils). If you are using Python version 3.3, use imp rather than importlib.

Now try re-executing that print() statement:

>>> stringutils.extract_numbers("Hell1o 123.543 -10.6 5 there")
['1o', '123.543 ', '-10.6 ', '5 ']

That's much better—our program now runs without crashing. There is, however, one more problem we need to fix: when we extract the characters that make up a number, we're extracting one character too many, so the number 1 is being returned as 1o and so on. To fix this, remove the +1 from line 7 of your source file:

        number = s[match.start():match.end()]

Then, reload the module again and re-execute your print() statement. You should see the following:

['1', '123.543', '-10.6', '5']

Perfect! If you wanted to, you could use the float() function to convert these strings into floating-point numbers, but for our purposes this module is now finished.

Let's take a step back and review what we've done. We had a module with mistakes in it, and used the Python interactive interpreter to help identify and fix these problems. We repeatedly tested our program, noticed a mistake, and fixed it, using a RAD approach to quickly find and correct the bugs in our module.

When developing your modules and packages, it's often helpful to test them in the interactive interpreter to find and fix problems as you go along. You just have to remember that every time you make a change to a Python source file, you'll need to call importlib.reload() to reload the affected module or package.

Using the Python interactive interpreter in this way also means that you have the complete Python system available for your testing. For example, you could use the pprint module in the Python Standard Library to pretty-print a complex dictionary or list so that you can easily view the information being returned by one of your functions.

There are some limitations, however, in the importlib.reload() process:

  • Imagine that you have two modules, A and B. Module A uses the from B import... statement to load functionality from module B. If you then change module B, the changed functionality won't be used by module A unless you reload that module too.
  • If your module crashes while initializing itself, it can end up in a strange state. For example, imagine that your module includes the following top-level code, which is supposed to initialize a list of customers:
    customers = []
    customers.append("Mike Wallis")
    cusotmers.append("John Smith")

    This module will be imported, but because of the misspelled variable name it will raise an exception during initialization. If this happens, you will need to firstly use the import command in the Python interactive interpreter to make the module available, and then use imp.reload() to load the updated source code.

  • Because you have to either type the commands yourself or select a command from the Python command history, it can get tedious to run the same code over and over, especially if your test involves more than a couple of steps. It's also very easy to miss a step when using the interactive interpreter.

For these reasons, it is best to use the interactive interpreter to fix specific problems or to help you rapidly develop a particular small piece of code. Custom written scripts work better when the tests get complicated or if you have to work with multiple modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.46.92