Advanced Python Programming (File I/O, Modules, Exceptions)

Chapter 6
Modules, Namespaces, and Imports

Diploma in Programming Languages (Java & Python)
Advanced Python Programming (File I/O, Modules, Exceptions)   •   Diploma

Modules, Namespaces, and Imports

Introduction

A Python program becomes difficult to maintain when every function, class, constant, and statement is placed in one large file. Modules provide a disciplined solution. A module is a Python file containing definitions and executable statements that can be loaded and reused by other programs. By dividing a system into modules, developers can reduce complexity, control names, test components independently, and collaborate more effectively.

This chapter examines how Python creates and uses modules, how names are stored in namespaces, how the import system locates and caches modules, and how professional programmers organize reusable code. Particular attention is given to __name__, import styles, package structure, circular imports, and design practices that make code dependable.

Learning Objectives

By the end of this chapter, you should be able to:

  • create a reusable Python module;
  • distinguish a module, package, namespace, and scope;
  • explain how Python resolves an import;
  • describe the purpose of sys.path and sys.modules;
  • use qualified, selective, aliased, and dynamic imports appropriately;
  • explain the role of __name__ and the if __name__ == "__main__" idiom;
  • identify causes and solutions of circular imports;
  • design modules with clear interfaces and minimal unnecessary coupling;
  • apply professional practices for reusable and maintainable code.

1. Modules as Units of Program Organization

A module is normally a file whose name ends in .py. The file contains a namespace: a mapping between names and objects. For example, consider temperature.py:

ABSOLUTE_ZERO_C = -273.15


def to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32


def to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9

Another file can reuse these definitions:

import temperature

print(temperature.to_fahrenheit(25))
print(temperature.ABSOLUTE_ZERO_C)

The name temperature refers to a module object. The dot operator accesses names belonging to that module. This qualified style makes the origin of a name visible and reduces accidental name collisions.

A module may contain functions, classes, variables, constants, and executable statements. Although definitions are the main reason to create modules, top-level statements execute when the module is first imported. Consequently, module-level code should generally be limited to definitions, constant initialization, configuration, and carefully controlled setup.

Benefits of modules

Modules support several important engineering goals:

  1. Decomposition: A complex application can be divided into coherent components.
  2. Reuse: A tested function can be used by many programs.
  3. Encapsulation: Implementation details can remain inside a module.
  4. Namespace management: Names are grouped under a meaningful module name.
  5. Testing: Individual modules can be tested independently.
  6. Team development: Different developers can work on separate components.
  7. Dependency control: A module can declare and limit what it requires.

A good module usually has a focused responsibility. A file that contains database access, user-interface logic, mathematical utilities, and network communication is technically possible but difficult to understand and maintain.

2. Namespaces and Scope

A namespace is a mapping from names to objects. It is useful to think of a namespace as a labelled dictionary, although the implementation and lookup rules are more specific than an ordinary dictionary.

Python commonly uses the following namespaces:

NamespaceTypical contentsLifetime
Built-inlen, print, ExceptionUsually the interpreter session
Global/moduleNames defined at module levelWhile the module remains loaded
Enclosing functionNames in an outer functionDuring relevant function calls
Local/functionParameters and local variablesDuring a function call
ClassAttributes and methods defined in a class bodyWhen the class is created

A namespace is not identical to a scope. A namespace stores names; a scope is the region in which Python searches for a name. For ordinary name lookup inside a function, Python follows the LEGB sequence:

  • L — Local: the current function;
  • E — Enclosing: surrounding function scopes;
  • G — Global: the current module;
  • B — Built-in: names supplied by Python.

For example:

message = "module value"


def outer():
    message = "enclosing value"

    def inner():
        message = "local value"
        return message

    return inner()

The message returned by inner is found in the local namespace of inner. The global value does not override a nearer local value.

Module namespaces

When Python loads temperature.py, it creates a module object and places names such as ABSOLUTE_ZERO_C and to_fahrenheit in that module's namespace. They are not automatically placed in the importing program's namespace when using ordinary import temperature.

import temperature

# Correct: the function is qualified by its module.
result = temperature.to_celsius(68)

This separation is valuable because two modules can define functions with the same name without conflict:

import statistics
import geometry

statistics.mean([2, 4, 6])
geometry.mean_radius([3, 5, 7])

The qualified names identify both the operation and its source.

3. Creating and Importing Modules

A module is created simply by saving Python code in a .py file. Suppose a project contains:

weather_app/
    main.py
    weather.py

The file weather.py might contain:

DEFAULT_CITY = "London"


def describe(city, temperature):
    return f"{city}: {temperature}°C"

The application can use it as follows:

# main.py
import weather

report = weather.describe(weather.DEFAULT_CITY, 18)
print(report)

When Python encounters import weather, it performs an import operation. Broadly, the interpreter:

  1. checks whether the module is already loaded;
  2. searches for a suitable module or package;
  3. creates a module object;
  4. executes the module's code in its namespace;
  5. stores the loaded module for future use;
  6. binds the module name in the importing namespace.

The exact mechanism is implemented by Python's import machinery, but this sequence gives a reliable conceptual model.

Selective imports

A specific name can be imported directly:

from weather import describe

print(describe("Paris", 21))

This places describe directly in the importing namespace. It is concise, but the source of the name is less visible. It also creates a possible collision if the importing file already has a name called describe.

An alias provides a shorter or clearer name:

import temperature as temp
from datetime import datetime as DateTime

Aliases are useful when a module name is long, when two modules have similar names, or when a widely recognized convention exists. They should not be used merely to obscure a dependency.

Wildcard imports

The statement below imports names into the current namespace:

from weather import *

Wildcard imports are generally discouraged in application and library code because they make it difficult to determine where names came from and may overwrite existing names. They also make static analysis and maintenance harder. A module can define __all__ to identify names intended for wildcard import, but this does not remove the broader disadvantages.

4. Import Resolution

Import resolution is the process through which Python determines what module a statement refers to. The search path is exposed through sys.path:

import sys

for entry in sys.path:
    print(entry)

The entries may include the script directory, configured library directories, installed-package locations, and other paths supplied by the environment. The precise initial contents depend on how Python was started and on environment configuration.

Python does not search every folder on the computer. It searches locations available through its import system. Therefore, a file may exist but still fail to import if its containing directory is not on the relevant search path.

Absolute and relative imports

An absolute import identifies a module from the top level of the importable package structure:

from application.services import billing

A relative import begins with one or more dots and is interpreted relative to the current package:

from .validators import validate_email
from ..common import logging_tools

Relative imports are useful inside packages because they express relationships within that package. They normally require the file to be imported as part of a package rather than executed as an isolated script.

Absolute imports are often easier to understand in large applications, while relative imports can make package-internal relationships explicit. A project should adopt a consistent style.

Shadowing and naming hazards

A local file can accidentally hide a standard-library or third-party module. For example, naming a file json.py, random.py, or email.py can cause confusing behavior when another program imports that name. A variable or function can also shadow an imported name:

import math

math = "not the math module"
# math.sqrt(9) now fails because math was rebound.

Good naming, clear project structure, and avoidance of generic filenames reduce these problems.

5. Module Caching and Repeated Imports

Python maintains a cache of loaded modules in sys.modules:

import sys
import weather

print("weather" in sys.modules)

After a module has been successfully loaded, a later import normally reuses the existing module object rather than executing the file again. This has two important consequences:

  • module initialization generally occurs once per interpreter process;
  • imports are normally cheaper after the first successful load.

Consider:

# counter_module.py
print("module initialized")
count = 0
import counter_module
import counter_module

Under normal conditions, module initialized is printed once. Both import statements refer to the cached module object.

The cache is not a permanent database. It belongs to the current interpreter process. Starting a new process creates a new cache. A developer can use importlib.reload() to re-execute a module, but reloading has limitations: existing references may still point to old objects, state may be duplicated, and dependent modules are not automatically rebuilt in a fully consistent way. Reloading is therefore mainly useful in controlled interactive or development situations, not as a general application update mechanism.

Module caching also explains why top-level side effects are risky. If a module opens a file, modifies global configuration, registers handlers, or starts a thread during import, those actions occur during initial loading and may be difficult to control or repeat safely.

6. The __name__ Variable and Script Entry Points

Every loaded module receives a special global variable named __name__. When the file is imported, __name__ usually contains the module's qualified name. When the file is run directly as the program entry point, Python sets __name__ to `

Multiple Choice Questions with Answers

Question 1

Which statement best describes a Python module?

A. A directory that can contain only classes

B. A Python file with its own namespace

C. A temporary variable created inside a function

D. A command-line argument supplied to the interpreter

Correct Answer: B. A Python file with its own namespace

Question 2

What does import package.tools normally bind in the importing namespace?

A. Every name defined inside tools

B. Only the name package

C. The name tools as a local variable, without package

D. A reference to the imported package structure, accessed through package.tools

Correct Answer: D. A reference to the imported package structure, accessed through package.tools

Question 3

Which sequence represents ordinary Python name lookup inside a function?

A. Global, Local, Built-in, Enclosing

B. Local, Enclosing, Global, Built-in

C. Built-in, Global, Enclosing, Local

D. Enclosing, Built-in, Local, Global

Correct Answer: B. Local, Enclosing, Global, Built-in

Question 4

What is the primary purpose of sys.modules?

A. It stores command-line arguments

B. It stores modules that have been loaded in the current interpreter

C. It lists all functions defined in the current file

D. It converts relative imports into absolute imports

Correct Answer: B. It stores modules that have been loaded in the current interpreter

Question 5

What is the usual effect of importing the same module twice in one interpreter process?

A. The module is normally reused from the module cache

B. The module is always executed twice

C. The second import deletes the first module

D. The second import automatically reloads all dependent modules

Correct Answer: A. The module is normally reused from the module cache

Question 6

Why is from module import * generally discouraged?

A. It prevents the module from being cached

B. It works only for built-in modules

C. It can obscure name origins and cause collisions

D. It imports only private names

Correct Answer: C. It can obscure name origins and cause collisions

Question 7

What is the value of __name__ in a file executed directly as the program entry point?

A. "main"

B. "__main__"

C. The filename including .py

D. None

Correct Answer: B. "__main__"

Question 8

Which design most directly reduces circular-import problems?

A. Make every module import every other module

B. Move shared abstractions into a lower-level module

C. Use wildcard imports everywhere

D. Place all code in one function

Correct Answer: B. Move shared abstractions into a lower-level module

Question 9

What does a relative import such as from .validators import check indicate?

A. The import refers to a module in the current package

B. The import refers only to the Python standard library

C. The import loads a module from the operating system root

D. The import can be used only inside a function

Correct Answer: A. The import refers to a module in the current package

Question 10

Which statement about module-level executable code is most accurate?

A. It never runs during import

B. It normally runs when the module is first imported

C. It runs only after every function in the module is called

D. It is automatically converted into comments

Correct Answer: B. It normally runs when the module is first imported

Advanced Python Programming (File I/O, Modules, Exceptions)
Chapter 6 of 13