Foundations: Python, Git and toolsLoading progress…

Modules and imports

Organize a small program into modules and classes while handling files, failures and iteration.

A file of definitions you can reuse

A module is just a Python file. If prices.py defines total and TAX, another file can say import prices and use prices.total(...). Modules give related code a name and keep a big program from becoming one long file where every name collides with every other.

What import actually does the first time, in order: find the file (the folders in sys.path), create a module object, run the file top to bottom once to fill that object with names, store it in the cache sys.modules, and bind a name in your file. Every later import prices anywhere in the program skips straight to the cache.

Try it: import, from-import and the cache

Click statements in any order. Each one runs in the same fresh interpreter.

Module file prices.py

TAX = 0.2print("prices: loading")def total(items): return round(sum(items) * (1 + TAX), 2)

Statements run so far

none yet

Import cache (sys.modules)

prices is not loaded

Your names

no names yet

Output

Honest note: this panel is a small simulation written for the lesson, checked against Python's real import system. The practice below uses the real import machinery on a file in a memory file system.

Click statements in any order. Watch the loading message appear only once, and compare the copy TAX with the live prices.TAX after you change the module.
  • import prices binds one name, prices, to the module object. You reach things as prices.TAX.
  • from prices import TAX binds a new name TAX to the value the module has right then. It is a copy of the reference, so later prices.TAX = 0.3 does not move your TAX.
  • But the function total reads TAX from inside the module, so it sees the new value. Same module, two different answers depending on which name you read.

Predict first. You run import prices, then from prices import total. How many times does prices: loading print?

Importing should not launch the program

Because importing runs top-level code, a module that starts a checkout at the bottom will start it for everyone who imports it, including tests. The convention is to put the work in a function and guard the call. When you run a file directly, Python sets its __name__ to "__main__". When it is imported, __name__ is the module name.

# prices.py
TAX = 0.2

def total(items):
    return round(sum(items) * (1 + TAX), 2)

def main():
    print("total:", total([10, 20]))

if __name__ == "__main__":     # true for: python prices.py
    main()                     # false for: import prices
Chapter context

As a program grows, the difficult part becomes keeping state and responsibilities understandable. Use simple functions for transformations, classes for related state and behavior, and modules to give those responsibilities names.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

Reason about state ownership before adding abstraction. For a transformation f, repeated calls should have independent results unless shared state is explicitly intended. A lazy iterator retains only the state required for its next step; it does not imply that downstream consumers also use constant memory.

Worked example

An inventory starts with 5 items. reserve(2) leaves 3. A second independent inventory still contains 5. If both objects instead refer to the same list, mutating one changes what both observe. Draw object references as arrows to make this distinction explicit.

Reference code

The following example uses the Python standard library and does not call a remote service.

class Inventory:
    def __init__(self, count):
        self.count = count
    def reserve(self, n):
        if n > self.count:
            raise ValueError("insufficient stock")
        self.count -= n
first, second = Inventory(5), Inventory(5)
first.reserve(2)
assert (first.count, second.count) == (3, 5)

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Type annotations are not validation. Catching Exception and continuing can hide corrupt state. Lazy iteration does not make a later list(...) memory-free.