runtime
107 / 250
lesson 107 Modules & Packages · intermediate

Your own file as a module

Any .py file is a module. `import pricing` finds pricing.py next to your program, runs its top-level code once, and shares that one module object with every file that imports it.

You'll be able to
  • Import a module of your own and use its functions and constants
  • Explain why the module's top-level code runs only on the first import
  • Use module-level state that every importer shares, and know when a function reads a module constant
The idea

Once two programs need the same function, copying it means fixing every bug twice. Put it in its own file instead: pricing.py is a module named pricing, and import pricing in any program in the same folder gives access to everything it defines.

The first import pricing finds the file (Python searches the folder of the program you ran first), runs its top-level code from top to bottom, and stores the finished module in a cache (sys.modules). Every later import, from any file, gets that same module object without running it again.

About this runner: in a real project you'd create pricing.py in your editor next to main.py. Here each run starts in an empty folder with one editor, so the examples create the module file from code first and call importlib.invalidate_caches(), which tells Python to look at the folder again because the file appeared after the program started. Everything from import pricing on is exactly what happens in a multi-file project.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · A pricing module, imported twice
from pathlib import Path
import importlib

Path("pricing.py").write_text('''TAX_RATE = 0.07
print("pricing module loaded")

def with_tax(amount):
    return amount * (1 + TAX_RATE)
''', encoding="utf-8")
importlib.invalidate_caches()

import pricing
import pricing
print(f"{pricing.with_tax(66.49):.2f}")
print(pricing.TAX_RATE, pricing.__name__)
example-2.py · A name the module doesn't define
from pathlib import Path
import importlib

Path("pricing.py").write_text("TAX_RATE = 0.07\n", encoding="utf-8")
importlib.invalidate_caches()

import pricing
print(pricing.with_tax(10))

Because a module runs once and is shared, a variable at module level is shared too. Here inventory holds the stock, and checkout (another module) imports it and takes stock away. The main program sees the change, because there is only one inventory.

example-3.py · Two modules, one shared inventory
from pathlib import Path
import importlib

Path("inventory.py").write_text('''STOCK = {"HOM-52782": 4, "GRO-63674": 12}

def take(sku, quantity):
    if STOCK.get(sku, 0) < quantity:
        return False
    STOCK[sku] -= quantity
    return True
''', encoding="utf-8")
Path("checkout.py").write_text('''import inventory

def buy(sku, quantity):
    return "paid" if inventory.take(sku, quantity) else "out of stock"
''', encoding="utf-8")
importlib.invalidate_caches()

import checkout
import inventory
print(checkout.buy("HOM-52782", 3), checkout.buy("HOM-52782", 3))
print(inventory.STOCK)
print(checkout.inventory is inventory)

When to use it: as soon as code is reused by more than one program, or a file grows too long to read. Give modules plain lower-case names. Mistakes to avoid: naming your file after a standard module (json.py, csv.py), doing slow work or printing at module level (it happens on import), and expecting edits to a module to show up in a program that already imported it (it was cached).

Review
  • A .py file is a module; import name runs it once and caches it, and every importer shares that one object.
  • Module-level constants and dictionaries are shared state; functions read module names when they run.
  • Keep modules to definitions: top-level work runs on import.
  • Next: a folder of modules, a package.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Your own file as a module — Modules & Packages · Runtime