Modules

Note

Be sure to change directories to the python_class folder and activate your virtual environment in your command/terminal window before running Python or starting the Python REPL. You will need to do this every time you open a new command/terminal window for this class.

CLI Script

We’ve typed all of our code at the REPL up to now. Let’s save our code in a file and make a command-line script.

Open your favorite code editor and create a file in your python_class folder called greetings.py which contains the following code:

print("Hello world!")

Once we’ve saved our file, we can go to our terminal window, and run:

$ python greetings.py
Hello world!

We’ve just made a Python program, also known as a module. We can also import .py files and use them as modules in other modules or in the REPL. Let’s open the Python REPL and import the file we just created (note we don’t include the ".py" when importing):

>>> import greetings
Hello world!

Cool, it works! However, in the real world, it’s not good that our module has side effects like printing the greeting when all we’ve done is import the file.

Our module should never do anything noticeable at import-time. It should only do things when we ask it to. But when a module is imported, Python executes all code in the module, so our greeting was printed. We need a way to prevent this from happening.

To avoid having our module print “Hello world” on import, we can check the __name__ variable on import. Note that there are two underscores before and after the variable name. These types of variables are often called “magic” variables or “dunder” variables. As a verbal shorthand, we will say “dunder name” to mean “double underscore name double underscore”. Let’s change our code to this:

greeting = "Hello world!"
if __name__ == "__main__":
    print(greeting)

When we run a file as a program from a command line, __name__ is always "__main__", but when it is imported, __name__ is equal to the name of the module, in this case "greetings". Let’s call our script again and make sure it still works:

$ python greetings.py
Hello world!

Okay, great! Now let’s open up a new Python prompt (exit the REPL and then type python again) and import again. You can see it does not print the greeting when we import it.

>>> import greetings
>>> greetings.greeting
'Hello world!'

Note that in order to display the value of the variable greeting, we have to prepend the module name. This is because the variable greeting belongs to the namespace of the module it is contained in, namely greetings. We can restrict our import to specific variables from a module by using the import form from X import Y:

>>> from greetings import greeting
>>> greeting
'Hello world!'

If there were other variables in the module greetings, we would not be able to access them, because they have not been imported.

CLI Arguments

Let’s change our command-line script so that it can accept arguments too. We need to import the sys module to find our arguments. It is a standard library module that comes with Python.

sys.argv is a list variable where the first element is the name of our script and all subsequent elements are the arguments given on the command line.

We will talk about lists more later in the class; for now just know that list elements are designated by square brackets, [], and lists use zero-based indexing, so that [0] is the first element of a list and [1] is the second element.

Let’s modify our script to use sys.argv to print out the first command-line argument:

import sys

if __name__ == "__main__":
    print("Hello {}!".format(sys.argv[1]))

Now we can pass arguments to our script:

$ python greetings.py world
Hello world!
$ python greetings.py earthlings
Hello earthlings!

But what happens when someone tries to run our program and forgets to put the argument?

$ python greetings.py
Traceback (most recent call last):
  File "greetings.py", line 4, in <module>
    print("Hello {}!".format(sys.argv[1]))
IndexError: list index out of range

This is not very friendly! Let’s change our program to check for this.

We want to make sure that the script has more than just the first element in the sys.argv variable. To do this we could use the built-in len function to check the length of sys.argv and make sure it is greater than 1.

However, it would be more Pythonic to get a list containing everything but the first element from sys.argv and check whether that list is empty. We can use a slice for that, which we will explain in the next class:

import sys

arguments = sys.argv[1:]
if __name__ == "__main__":
    if arguments:
        print("Hello {}!".format(arguments[0]))
    else:
        print("Greetings, my absent-minded friend!")

Now when we run it without arguments, we don’t get an error:

$ python greetings.py
Greetings, my absent-minded friend!
$ python greetings.py Pete
Hello Pete!

Executable Scripts

When working on a UNIX-like system such as Linux or Mac, you can make Python programs into executable scripts.

We can do this by adding a special comment to the first line of our file. This is usually referred to as a “shebang”:

#!/usr/bin/env python3
import sys

if __name__ == "__main__":
    print("Hello {}!".format(sys.argv[1]))

This will run our code with Python 3 when we execute it as a script from the command-line.

In order to actually execute our code as a script, we need to make the file executable:

$ chmod a+x greetings.py

Now that the file has execute permission, we can run our script:

$ ./greetings.py World
Hello World!

Putting the ./ before the script name forces it to be executed by the system. Without it, the system thinks it is a shell command, rather than a script.

Module Exercises

Allow Zero Arguments

Make a script like our greetings.py file, but allow it to take zero arguments. When no arguments are given, it should say “Hello world!”

CLI Only

Make a module that prints “Hello world” when you use it from the command-line, but prints an error message when you try to import it. It should work like this:

$ python hello.py
Hello world
>>> import hello
This module can only be run from the command-line

For bonus points, make the module raise an error when importing instead of just printing a message. You can use this line to raise this error:

>>> raise ImportError("This module can only be run from the command-line")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: This module can only be run from the command-line

Add

Make a program add.py that takes two numbers and prints out the sum of these numbers.

$ python add.py 3 5.2
8.2
$ python add.py -7 2
-5.0
$ python add.py -2 -1
-3.0

Difference Between

Make a program difference.py that takes two numbers and prints the positive difference between these two numbers:

$ python difference.py 3 5
2.0
$ python difference.py 6 3.5
2.5
$ python difference.py -7 2
9.0

Allow Unlimited Arguments

Make a script like our greetings.py file that takes any number of arguments. All arguments should be joined together by spaces and printed out.

Your script should work like this:

$ python greetings.py
Hello!
$ python greetings.py world
Hello world!
$ python greetings.py from planet Earth
Hello from planet Earth!

Tip

You may have noticed we did this in an example earlier, but you can get a list of all arguments with sys.argv[1:]. We’ll explain how this works in the next class.

Hint

Use str.join on the sys.argv list.