Previous | Tutorial index | Next

Tutorial 3: Importing Modules – Using the import Statement

Learning Objectives

Import and use modules already in the Python programming/development environment.

1. Introduction: Unlocking the Toolbox

In Tutorials 1 and 2, you learned what modules and packages are and how they are stored on your file system. But creating a module is only half the story. The real power comes when you import that module into another script or interactive session to use its functionality.

Think of importing like opening a toolbox: the tools (functions, classes, variables) are inside, but you can't use them until you open the box and take them out. In Python, the import statement is your key. It gives you access to thousands of built-in, standard library, and third-party tools. Mastering the various ways to import is essential for writing clean, efficient, and maintainable code.

2. The Basic import Syntax

The simplest and most common way to use a module is with the basic import statement.

Syntax:

import module_name

What happens when you run this?

  1. Python searches for the module (using sys.path).
  2. Python compiles the module (if it's not compiled already).
  3. Python executes the code inside the module from top to bottom.
  4. Python creates a namespace (a named container) for the module, using the module's name.
  5. Python creates a reference to that namespace in your current scope, bound to the module name.

Example:

import math # Accessing content using dot notation print(math.pi) # 3.141592653589793 print(math.sqrt(16)) # 4.0 print(math.floor(3.7)) # 3

The Dot Notation (.) - Accessing the Namespace

The dot (.) is the access operator. It tells Python: "Look inside the math namespace and find the attribute named sqrt."

This is the safest import method because it keeps the module's contents isolated in its own namespace. If you have a function called floor() in your own code, it won't conflict with math.floor() because they are in different namespaces.

The dir() Function - Exploring Module Contents

You can use dir() to see all the names (functions, classes, constants, and submodules) available inside a module.

import math print(dir(math)) # Output: ['__doc__', '__loader__', '__name__', '__package__', ..., 'acos', 'acosh', 'asin', ..., 'pi', 'sqrt', ...]

This is incredibly useful for discovery and learning, especially when you are working with an unfamiliar module.

The help() Function - Getting Documentation

You can use help() to read the official documentation of a module or a specific function directly in your interactive session.

import math help(math.sqrt) # Output: Help on built-in function sqrt in module math: # sqrt(x, /) # Return the square root of x.

3. Using Aliases with the as Keyword

Sometimes, module names are long (e.g., matplotlib.pyplot), or you might want to use a standard abbreviation to make your code more concise. The as keyword allows you to assign a custom name (an alias) to the imported module.

Syntax:

import module_name as alias_name

Example:

import numpy as np import pandas as pd import matplotlib.pyplot as plt # Now you can use the aliases array = np.array([1, 2, 3]) print(array)

When to use aliases:

# Resolving a conflict between two potentially conflicting module names import my_utilities as mu import their_utilities as tu mu.calculate() # Uses your function tu.calculate() # Uses their function

4. Where Does Python Look? (The Search Order)

When you type import something, Python follows a precise order to locate the module. Understanding this order is critical for troubleshooting ModuleNotFoundError.

The Search Path Sequence

  1. Built-in Modules: Python first checks if the module is a built-in module (like sys, math, os). These are written in C and are hardcoded into the interpreter. This check happens very fast.

  2. The Current Directory: Python then looks in the directory containing the script you are running (or the current working directory if you're in an interactive session). This is the first entry in sys.path.

  3. PYTHONPATH Environment Variable: Python then checks the directories listed in the PYTHONPATH environment variable (if you have set one). This allows you to define custom global directories for your own libraries.

  4. Standard Library Directories: Python then searches the default directories where the Python standard library is installed (e.g., /usr/lib/python3.10/ on Linux or C:\Python310\Lib on Windows).

  5. Site-packages Directories: Finally, Python searches the site-packages directories, where all third-party modules and packages are installed via pip.

Viewing sys.path

You can print sys.path to see the exact search order on your system:

import sys for path in sys.path: print(path)

Key Point: If you create a module with the same name as a built-in module (e.g., math.py) and save it in your current directory, Python will find your math.py first, because the current directory has higher priority than the built-in modules. This can lead to very confusing bugs. Best practice: Never name your files after built-in modules.

Modifying sys.path at Runtime

You can programmatically add directories to sys.path to allow Python to find modules in non-standard locations.

import sys sys.path.append("/path/to/my/custom/modules") import my_module # Now this will work if my_module is in that folder

However, this is generally considered a hack and not recommended for production code. It's better to structure your project properly or set the PYTHONPATH environment variable.

5. Viewing Module Locations with __file__

Most modules (except built-in ones) have a __file__ attribute that tells you the exact path of the .py file (or .pyc bytecode file) that Python loaded.

Example:

import math print(math.__file__) # Raises AttributeError: 'math' has no attribute '__file__' because it's a built-in C module. import os print(os.__file__) # Output: /usr/lib/python3.10/os.py (or similar path)

This is very useful when:

6. Namespace Implications and Variable Shadowing

When you use import module, the module's name becomes a variable in your current namespace. Be careful not to accidentally reassign it, as that will break your ability to access the module later.

Example of a mistake:

import math math = 10 # Oops! Now 'math' is an integer. You can no longer do math.sqrt()! print(math.sqrt(4)) # This will raise AttributeError: 'int' object has no attribute 'sqrt'

Best practice: Avoid using common module names as variable names in your scripts.

7. Quiz: Check Your Understanding

Test your knowledge before moving to the practical exercises.


Q1: Which keyword is used to give a module a shorter or alternative name during import?

A) as B) alias C) rename D) with

AnswerA) `as`

Q2: In which order does Python search for a module?

A) Standard Library → Built-in → Current Directory → Site-packages B) Current Directory → Built-in → Site-packages → Standard Library C) Built-in → Current Directory → PYTHONPATH → Standard Library → Site-packages D) PYTHONPATH → Current Directory → Built-in → Site-packages

AnswerC) Built-in → Current Directory → `PYTHONPATH` → Standard Library → Site-packages

Q3: What attribute of a module shows you the file location from which it was loaded?

A) __path__ B) __file__ C) __location__ D) __source__

AnswerB) `__file__`

Q4: True or False: If you have a script named random.py in your current directory, and you type import random, Python will always import the standard library random module.

AnswerFalse. Python will import your local `random.py` because the current directory is searched before the standard library.

Q5: What is the output of the following code?

import math print(type(math))

A) <class 'module'> B) <class 'function'> C) <class 'math'> D) <class 'file'>

AnswerA) ``. In Python, modules are objects of type `module`.

8. Hands-on Exercises (Practical Tasks)

These exercises will make you comfortable with importing, exploring, and working with modules in a live environment.


Exercise 1: Importing and Exploring Built-in Modules

Goal: Import math and random, explore their contents, and use their functions.

Instructions:

  1. Open your Python interactive interpreter.
  2. Type import math and press Enter.
  3. Type dir(math) and look for the constants pi and e.
  4. Type help(math.degrees). Read what it does.
  5. Calculate the sine of 90 degrees (remember, math.sin() takes radians, so you need math.radians(90) or use math.pi/2).
  6. Now import the random module: import random.
  7. Use random.randint(1, 6) to simulate rolling a die 10 times.
  8. Use random.choice(['red', 'green', 'blue']) to randomly pick a color.
Sample Output ``` >>> import math >>> dir(math) ['__doc__', '__loader__', '__name__', '__package__', ..., 'acos', 'acosh', 'asin', ..., 'pi', 'sqrt', ...] >>> help(math.degrees) Help on built-in function degrees in module math: degrees(x, /) Convert angle x from radians to degrees. >>> math.sin(math.radians(90)) 1.0 >>> import random >>> random.randint(1, 6) 4 >>> random.choice(['red', 'green', 'blue']) 'green' ```

Exercise 2: Aliasing in Action

Goal: Practice creating and using aliases.

Instructions:

  1. Import the datetime module with the alias dt.
  2. Use dt.datetime.now() to get the current date and time.
  3. Now import the json module with the alias js.
  4. Create a Python dictionary: data = {"name": "Alice", "age": 30}.
  5. Use js.dumps(data) to convert it to a JSON string.
  6. Try to access the json module using its original name (json.dumps(data)). What error do you get? Why?
Sample Output ``` >>> import datetime as dt >>> dt.datetime.now() datetime.datetime(2025, 1, 15, 14, 30, 45, 123456) >>> import json as js >>> data = {"name": "Alice", "age": 30} >>> js.dumps(data) '{"name": "Alice", "age": 30}' >>> json.dumps(data) Traceback (most recent call last): File "", line 1, in NameError: name 'json' is not defined ``` The error occurs because the name `json` was never bound in the current namespace. When you import with `as`, you bind the alias (`js`), not the original name.

Goal: Explore the search path and see what happens when you create a shadowing module.

Instructions:

  1. Import sys and print sys.path to see all the directories Python searches.

  2. Identify which directory is listed first (it should be an empty string '' or the current working directory).

  3. Now, using your code editor, create a new file named random.py in your current directory with this content:

    # random.py print("This is my custom random module!") def randint(a, b): return 42 # Always returns 42
  4. Save the file and close it.

  5. In the Python interpreter (make sure it's running in the same directory), type import random.

  6. Observe the print message "This is my custom random module!" – this proves Python loaded your file, not the built-in one.

  7. Type random.randint(1, 100). What does it return? (It returns 42).

  8. Delete the random.py file (or move it away) and restart the interpreter. Now import random will load the standard library module again.

Sample Output ``` >>> import sys >>> sys.path[0] '' # or the full path to the current directory >>> import random This is my custom random module! >>> random.randint(1, 100) 42 ```

Exercise 4: Module Introspection with dir() and help()

Goal: Use dir() and help() to learn about an unfamiliar module.

Instructions:

  1. Import the os module.
  2. Use dir(os) to list everything inside it.
  3. Look for functions related to file operations (e.g., listdir, rename, remove).
  4. Use help(os.listdir) to read about the listdir function.
  5. Use os.listdir('.') to list all files and folders in your current directory.
Sample Output ``` >>> import os >>> dir(os) ['DirEntry', 'F_OK', ..., 'listdir', 'rename', 'remove', ...] >>> help(os.listdir) Help on built-in function listdir in module nt: listdir(path=None) Return a list containing the names of the entries in the directory. >>> os.listdir('.') ['main.py', 'random.py', 'vehicles', ...] ```

9. Common Pitfalls and Troubleshooting

Problem Likely Cause Solution
ModuleNotFoundError: No module named 'xyz' The module is not installed or Python cannot find it. Check the spelling. If it's a third-party module, install it with pip install xyz. If it's your own module, ensure it's in the right directory or add its path to sys.path.
ImportError: cannot import name 'xyz' You used from module import xyz, but xyz doesn't exist in that module. Check the spelling and case of the name. Use dir(module) to see the correct names.
Your import works in the terminal but fails in an IDE (like VS Code). The IDE is running the script from a different working directory. The current directory differs. Set the correct working directory in your IDE's run configuration, or use absolute paths in your project structure.
AttributeError: module 'xyz' has no attribute 'func' You imported a module, but you are trying to access a name that doesn't exist there. Check for typos. If you recently added the function to the module, restart the Python interpreter to clear the cache.
Module code seems to run twice. You might be importing the module in multiple places, but more commonly, you have a script that is both run directly and imported elsewhere, causing its top-level code to execute. Use the if __name__ == "__main__": guard to prevent top-level code from running on import.

10. Homework Questions (Take-Home Assignment)

These questions are designed to test your practical ability and conceptual understanding.


Homework Question 1: Building a Multi-Module Program

Task: Create two modules in the same directory:

Now, write a separate script called main.py in the same directory. In main.py:

Submission: Provide the full code for calculator.py, math_info.py, and main.py.

Sample Solution

calculator.py:

def add(a, b): return a + b def subtract(a, b): return a - b def multiply(a, b): return a * b def divide(a, b): if b == 0: return "Error: Division by zero!" return a / b

math_info.py:

CONSTANTS = { "pi": 3.14159, "e": 2.71828 } def describe(): print("This module provides mathematical constants.")

main.py:

import calculator as calc import math_info print("Calculator Results:") print(f"10 + 5 = {calc.add(10, 5)}") print(f"10 - 5 = {calc.subtract(10, 5)}") print(f"10 * 5 = {calc.multiply(10, 5)}") print(f"10 / 5 = {calc.divide(10, 5)}") print() print("Math Info:") print(f"PI = {math_info.CONSTANTS['pi']}") math_info.describe()

Homework Question 2: Investigating the Search Path

Task: Write a Python script named search_path_demo.py that does the following:

  1. Imports sys.
  2. Prints out the sys.path list, formatted so that each directory is on its own line.
  3. Attempts to import a module named fantasy. Since this module doesn't exist, the script will raise a ModuleNotFoundError. Use a try-except block to catch this error and print a user-friendly message: "The module 'fantasy' could not be found. Please check your PYTHONPATH."
  4. After the try-except block, append a new, non-existent directory (e.g., /my_custom_libs) to sys.path.
  5. Print sys.path again to confirm the new directory was added.

Submission: Provide your search_path_demo.py script and a brief explanation (2-3 sentences) of why the current working directory is often the most important entry in sys.path for beginners.

Sample Solution
import sys print("Current sys.path:") for i, path in enumerate(sys.path): print(f"{i}: {path}") print("\nAttempting to import 'fantasy'...") try: import fantasy except ModuleNotFoundError: print("The module 'fantasy' could not be found. Please check your PYTHONPATH.") # Append a new directory sys.path.append("/my_custom_libs") print("\nUpdated sys.path:") for i, path in enumerate(sys.path): print(f"{i}: {path}")

Explanation: The current working directory (the first entry in sys.path) is crucial for beginners because when you save a Python script and run it from the terminal, the script's location automatically becomes the first place Python looks. This means any other .py files you create in the same folder can be imported without any extra configuration, making it very easy to organize code into multiple files.


Homework Question 3: Shadowing and Built-in Modules

Task: You have a project folder containing a file named os.py. Inside this file, you wrote:

# os.py def get_username(): return "Guest"

A colleague tries to run your project but complains that os.getcwd() doesn't work anymore.

Questions:

  1. Why does os.getcwd() fail when os.py exists in the project directory?
  2. What is the correct way to use both the built-in os module and your custom get_username() function in the same script, without renaming your os.py file? (Hint: Use aliases).
  3. Write a small script that successfully imports both your custom os.py (to call get_username()) and the standard library os (to call getcwd()). Explain what you had to do.
Sample Solution

1. Why os.getcwd() fails: When os.py exists in the project directory, Python finds this file first (because the current directory is searched before the standard library). The standard library os module is never imported, so os.getcwd() doesn't exist.

2. How to fix without renaming: Use aliases to distinguish between the two modules:

import os as std_os # Standard library import os as custom_os # Your custom os.py file

However, Python doesn't allow importing the same module name twice with different aliases in the same statement. You need to use different import strategies for each.

3. Working script:

# Use an alias for your custom module to avoid shadowing import os as std_os import os as custom_os # Try importing your custom module separately after renaming the file, or use: # Instead, rename your custom module to something unique like my_os.py # Better approach: Rename your custom file to my_os.py import my_os import os as std_os print(std_os.getcwd()) # Works: from standard library print(my_os.get_username()) # Works: from your custom module

Alternatively, you can rename your custom os.py file to my_os.py and use:

import os import my_os print(os.getcwd()) print(my_os.get_username())

Homework Question 4: Creating a Simple Data Converter Package

Task: You are building a data conversion utility. Create a package named converter with the following structure:

converter/ ├── __init__.py ├── length.py (functions: cm_to_inch, inch_to_cm) └── temperature.py (functions: c_to_f, f_to_c)

Submission: Provide all files in a zip archive or paste the contents of each file in your answer.

Sample Solution

converter/length.py:

def cm_to_inch(cm): return cm / 2.54 def inch_to_cm(inch): return inch * 2.54

converter/temperature.py:

def c_to_f(celsius): return (celsius * 9/5) + 32 def f_to_c(fahrenheit): return (fahrenheit - 32) * 5/9

converter/init.py:

from .length import cm_to_inch, inch_to_cm from .temperature import c_to_f, f_to_c

test_converter.py:

import converter cm_value = 5 print(f"{cm_value} cm = {converter.cm_to_inch(cm_value):.2f} inches") celsius_value = 100 print(f"{celsius_value}°C = {converter.c_to_f(celsius_value):.1f}°F")

Homework Question 5: Conceptual Essay – Namespaces and Dot Notation

Task: In your own words (300-500 words), explain the concept of a "namespace" in Python as it relates to modules.

Sample Answer

Answer: A namespace in Python is essentially a container that maps names to objects. When you import a module using import module, Python creates a namespace with the module's name and stores all the module's functions, classes, and variables inside it. This prevents naming conflicts between different modules by isolating their contents behind a prefix.

The dot notation module.function() is safer than wildcard imports (from module import *) because it maintains this separation. With dot notation, the origin of every function is immediately clear when reading the code. If two modules both define a calculate() function, module1.calculate() and module2.calculate() are unambiguous. With wildcard imports, the second import would overwrite the first, leading to silent bugs where the wrong function is called.

In a large team project, imagine two developers independently write helper modules: database.py and plotting.py. Both define a function named connect() (one connects to a database, the other connects points on a graph). Without dot notation, the team might not realize the conflict until the wrong connect() is called at runtime, causing subtle data corruption or misdisplayed graphs. With dot notation, the calling code explicitly states database.connect() and plotting.connect(), making the intended function obvious and preventing the bug entirely.

The as keyword provides additional namespace management by allowing developers to rename imports to avoid conflicts or improve readability. For example, import database as db creates a shorter, more convenient namespace. Similarly, from package import function as custom_function can differentiate between functions from different sources. This explicit control over naming makes code more maintainable and reduces the cognitive load on developers reading and debugging the code.

In summary, namespaces, dot notation, and aliases work together to create a clean, predictable, and safe environment for managing code dependencies.

11. Summary of Tutorial 3

This concludes the Tutorial 3. Once you have completed the exercises and homework, you will have a strong command of the import statement. You are now ready to move on to Tutorial 4, where we will dive into selective imports using from ... import ... and explore the nuances of importing specific parts from modules and packages!

Previous | Tutorial index | Next