Importing a module runs code

Modules

Modules are the tool we use for breaking up our code into multiple files in Python. When you write a .py file, you're making a Python module. You can import your own modules, modules included in the Python standard library, or modules in third-party packages.

0%
Watch other topic trails

Transcript

When Python imports a module, it runs all the code in that module.

A module that defines and prints things

Here we have a file called salutations.py:

import random

salutations = ["Hello", "Hey", "Hi", "Hiya", "Howdy"]

emphatic_salutations = []
for greeting in salutations:
    emphatic_salutations.append(greeting + "!")
salutations.extend(emphatic_salutations)

def greet():
    """Print a salutation."""
    print(random.choice(salutations))

print("Test calling greet:", greet())

def part():
    """Print a valediction."""
    print("Live long and prosper")

print("Test calling part:", part())

This salutions module imports random and defines a list (named salutations). It then defines another list named emphatic_salutations (based on the first list) and then it extends that first list.

Then it defines a function called greet, calls that function, and prints a statement. Lastly, it defines another function called part, calls that function, and prints another statement.

What happens when you import a module?

Now, what do you expect would happen if we were to import this salutations module?

>>> import salutations

Normally, what happens when you import a module in Python is we'd see nothing happen. For example, if we import the math module, we'll get a math module object back, but we wouldn't see anything happen at import time.

But when we import salutations, we see things printed out:

>>> import salutations
Hiya!
Test calling greet: None
Live long and prosper
Test calling part: None

That's a little weird.

This happens because when Python imports a module, it runs all the code in that module. After running the module it takes whatever variables were defined in that module, and it puts them on the module object, which in our case is salutations.

So within our salutations module, we have a greet function:

>>> salutations.greet()
Hiya!

And a part function:

>>> salutations.part()
Live long and prosper

We also have a salutations list:

>>> salutations.salutations
['Hello', 'Hey', 'Hi', 'Hiya', 'Howdy', 'Hello!', 'Hey!', 'Hi!', 'Hiya!', 'Howdy!']

In fact we even have a greeting variable:

>>> salutations.greeting
'Howdy'

Because

emphatic_salutations = []
for greeting in salutations:
    emphatic_salutations.append(greeting + "!")
salutations.extend(emphatic_salutations)

Even the random module is available on our salutations module object (because salutations.py imported the random module):

>>> salutations.random
<module 'random' from '/home/trey/.pyenv/versions/3.9.1/lib/python3.9/random.py'>

Seeing greeting and random on our salutations object is really weird, but this is just the way Python works.

Avoiding import side effects

When you import a module in Python, all the code in it will be run, and all the variables in that module will be stuck on that module object.

This normally isn't a problem unless we have side effects at import time. That's the problem in our case: our salutations module has side effects.

We're printing a few statements at the top-level of our salutations module:

def greet():
    """Print a salutation."""
    print(random.choice(salutations))

print("Test calling greet:", greet())


def part():
    """Print a valediction."""
    print("Live long and prosper")

print("Test calling part:", part())

But we probably shouldn't be printing at the top-level of our file.

If we remove those print calls:

def greet():
    """Print a salutation."""
    print(random.choice(salutations))

print("Test calling greet:", greet())


def part():
    """Print a valediction."""
    print("Live long and prosper")

print("Test calling part:", part())

And then we import our module in a new Python REPL, we'll see...

>>> import salutations

Nothing!

Nothing happens, except that we get a salutations module object, and it has the variables that are available in this module.

A general guideline: when you're making a Python file (a .py file), you shouldn't usually put print calls at the top-level of that file. Typically, the only thing you'll do at the top-level of a module is define functions, classes, or other objects (constants for example) that are meant to be used accessed on your module object (after your module has been imported). But there is one exception to this general rule: command-line programs.

When are import side effects acceptable?

The one place that you actually want to print at the top-level of your module is if you're making a command-line program (a.k.a. [a script][]).

Here we have a command-line program called greet.py:

import random
import sys

salutations = ["Hello", "Hey", "Hi", "Hiya", "Howdy"]


def greet(name):
    """Print a salutation."""
    print(random.choice(salutations), name)


arguments = sys.argv[1:]
if not arguments:
    greet("")
for name in arguments:
    greet(name)

This greet.py file is meant to be used as a command-line program:

$ python3 greet.py Trey
Hi Trey

Each time it prints a random greeting to the screen (using the random module):

$ python3 greet.py Trey
Hello Trey
$ python3 greet.py
Howdy

In this Python script we have some code that reads arguments (if they're given to us) and then calls our greet function (which prints something out):

arguments = sys.argv[1:]
if not arguments:
    greet("")
for name in arguments:
    greet(name)

This code is at the top-level of our module because we want to run this code every time our program runs. This module is not mean to be imported, so print calls at the top-level of our program are acceptable.

Summary

When you import a module, Python will run all the code that's in that module.

So if your Python file is meant to be imported as a module, be careful not to put side effects at the top-level of your .py file.