---
title: 'Reloadability and the Main Block'
source: 'https://academia.sh/en/courses/python-data-structures/reloadability-and-main-block'
course: 'Data Structures and Functional Tools'
language: en
updated: '2026-08-17T18:10:26+00:00'
license: 'CC BY-SA 4.0'
---

# Reloadability and the Main Block

The same file creates one module object when imported and a second when run as a script, and the main block runs only in the second; reload keeps the module object but renews every function inside it.

In the previous lesson, everything was imported: the library modules and the
measurement functions alike were defined inside a body and called from outside. Yet
the same file can work in two separate forms. One is the form measured up to here —
the file is requested as a **module**, its body runs, and its names are opened up
for others to use. The other is running it directly: the file is launched as a
**script** and becomes the program's entry point.

The same body runs in both forms. The only thing that differs is what gets written
into the module's **own name**, and a block that looks at that name runs in only one
case. This lesson's question is: when the same file is run in two forms, **how many
module objects** get created, which record holds which, and what happens to old
objects taken from a module when it gets reloaded?

## The Module's Own Name

Every module object's dictionary has an entry named `__name__`, and import fills
that entry with the module's **name in the cache**. When a file is imported, the
file's module name gets written there. When the same file is run directly, `"__main__"`
gets written there instead, because that run has no import name — it was not
requested, it was launched.

The **main block** is the condition that reads this distinction. A block at the end
of the body that compares `__name__` to `"__main__"` runs when the file is run as a
script, and does not run when it is imported. This is how a file can be both a
library and an entry point: definitions sit in the body and are open to everyone,
running sits inside the block and only works on a direct run.

This has a measurable consequence, and it does not contradict the rule established
in the first lesson, it follows from it: a cache record is kept **by name**. A file
run as a script gets registered under the name `__main__`, the same file imported
under its own name. Two separate names, two separate records, meaning **two separate
module objects** — from the same file.

## What Reload Changes

When the module cache drops a record, the next request creates a new object; this
was measured in the first lesson. **Reload**, by contrast, is a different job: it
reruns the body, but writes into **the existing module object's dictionary**. The
module object stays the same, the names inside it get refreshed.

The distinction has two observable consequences, and the measurement tests both:
because the body reruns, every definition produces **a new object**, and the write
is done **without emptying** the dictionary.

The measurement's assumptions:

- **MP29** — The measurement leaves no file in the repository; sources are produced
  in a temporary directory and **the directory's path is not printed.**
- **MP30** — The measured file writes its own name to a shared list on every run;
  the counting side is the measured file itself.
- **MP31** — The script run opens no separate process. It is run in the same
  interpreter, with the module's own name set to `__main__`; this is why the
  measurement can read the shared list.
- **MP32** — The main block only drops a record into the list; no other work is
  done inside it.
- **MP33** — Before reloading, **a single string** in the source text is changed;
  the only thing that changes is the value the function returns.
- **MP34** — Before reloading, a name is added to the module from outside, and that
  name's fate is separately read.
- **MP35** — The old function object is bound to a separate name before reloading;
  comparisons are made with `is` and **no identity number is printed.**
- **MP36** — When the measurement ends, the added path entry and all cache records
  are rolled back.

## Measurement

```python
"""Main block and reload: how many module objects does the same file create."""

import importlib
import runpy
import sys
import tempfile
from pathlib import Path

TOOL = """import log

log.EVENTS.append(('body', __name__))


def greet():
    return 'first'


if __name__ == '__main__':
    log.EVENTS.append(('main block', __name__))
"""
LOG_SOURCE = "EVENTS = []\n"


def setup(root):
    (root / "log.py").write_text(LOG_SOURCE, encoding="utf-8")
    (root / "tool.py").write_text(TOOL, encoding="utf-8")
    sys.path.insert(0, str(root))
    importlib.invalidate_caches()


def teardown(root):
    sys.path.remove(str(root))
    for name in ("log", "tool"):
        sys.modules.pop(name, None)


def measure(root):
    setup(root)
    log = importlib.import_module("log")
    tool = importlib.import_module("tool")
    import_run = list(log.EVENTS)

    log.EVENTS.clear()
    script = runpy.run_module("tool", run_name="__main__")
    script_events = list(log.EVENTS)
    separate_object = script["greet"] is not tool.greet
    cache_intact = sys.modules["tool"] is tool

    log.EVENTS.clear()
    tool.ADDED = "before reload"
    old_greet = tool.greet
    (root / "tool.py").write_text(TOOL.replace("'first'", "'second'"),
                                 encoding="utf-8")
    importlib.invalidate_caches()
    reloaded = importlib.reload(tool)
    result = {
        "import_run": import_run,
        "script": script_events,
        "separate_object": separate_object,
        "cache_intact": cache_intact,
        "reload_events": list(log.EVENTS),
        "same_module": reloaded is tool,
        "old_function": old_greet is tool.greet,
        "old_value": old_greet(),
        "new_value": tool.greet(),
        "added_survived": getattr(tool, "ADDED", "deleted"),
    }
    teardown(root)
    return result


with tempfile.TemporaryDirectory() as d:
    s = measure(Path(d))

print(f"ran during import: {s['import_run']}")
print(f"ran when run as a script: {s['script']}")
print(f"function in the script namespace is a separate object from the module's -> {s['separate_object']}")
print(f"the cache record is not disturbed -> {s['cache_intact']}")
print()
print(f"ran during reload: {s['reload_events']}")
print(f"module object is the same -> {s['same_module']}, "
      f"function object is the same -> {s['old_function']}")
print(f"the function bound to the old name gives {s['old_value']}, "
      f"the module's function gives {s['new_value']}")
print(f"name added before reload -> {s['added_survived']}")
```

```
ran during import: [('body', 'tool')]
ran when run as a script: [('body', '__main__'), ('main block', '__main__')]
function in the script namespace is a separate object from the module's -> True
the cache record is not disturbed -> True

ran during reload: [('body', 'tool')]
module object is the same -> True, function object is the same -> False
the function bound to the old name gives first, the module's function gives second
name added before reload -> before reload
```

## Two Runs, Two Objects

The first two lines give the main block's rule directly. The only thing that ran on
import is the body, and the module's own name is `tool`; there is **no** main block
record. Running as a script reruns the same body, but this time the module's own
name is `__main__`, and the main block runs too. Same file, same lines, two
different results — the only thing making the difference is the value written to
the name.

The third line shows the cost: the function in the script run's namespace is
**a separate object** from the module's function. Because the body ran a second
time, the `def` statement created a second function object. Module objects created
from the same file: **2**, shared between them: **0**. The fourth line completes
this: the `tool` record in the cache is not disturbed, it is still bound to the
first object. The two runs stand side by side and do not see each other.

This explains why writing work into the main block is limited. If a file gets both
imported and run as a script, the two runs produce **two separate namespaces**
carrying the same names. A counter or cache kept at module level gets created
twice; what gets written into one is not visible in the other. The only reason this
does not show up in the measurement is that the counted list sits not in `tool` but
in the `log` module both share. The rule is: **an entry point is a shell** — state
and definitions stay in the body, the main block only calls a function.

## Reload

The lower section measures a separate mechanism. What ran during reload is again
the body, and the module's own name stays as **`tool`** — this is the difference
from the script run. The next line gives the heart of the distinction: the module
object is **the same**, the function object is **not**. Because the body reran,
`def` created a new function object, and the name in the module's dictionary got
bound to it; the module object itself never changed.

The third line shows whose view this breaks. The side that took the function under
its own name before reloading holds the **old** object, and that object gives
**first**; the function called through the module gives **second**. Only those
reading the change from the module's dictionary saw it. Function objects created:
**2**, held: **2**, shared: **0**. The distinction the first lesson measured is
paid off again here: import shares the object, it copies the name — a copied name
does not get refreshed.

The last line confirms the dictionary was not emptied: the name added to the
module from outside before reloading **is still there**. Reload is not a cleanup,
it is an overwrite. Every name with no counterpart in the new body — a removed
definition, an attribute added from outside — keeps its old value.

What the two measurements say together is: **rerunning a source has three separate
outcomes.** If the cache record is removed and it is requested again, a new module
object gets created; if it is reloaded, the module object is kept, its contents get
renewed; if it is run as a script, a second object gets created under a second
name, and the first stays as it was. In all three, the body runs one more time, but
the objects created and held are different in all three.

## Summary

- The module's own name is filled with the module name on import, with
  `__main__` on a direct run; the main block only runs in the second case.
- When the same file is run in two forms, **2** module objects get created; the
  definitions the two namespaces share is **0**, and the cache record from the
  import is not disturbed.
- The main block is written as a shell: definitions and state stay in the body,
  because the body runs in both forms and every module-level piece of state gets
  created twice.
- Reload **keeps** the module object, reruns the body, and binds the names inside
  it to new objects; an old function object taken beforehand keeps giving its old
  value.
- Reload does not empty the dictionary; names with no counterpart in the new body
  stay in place with their old values.

## Course Wrap-Up

The course ran on a single question: how many objects did the written form
**create**, how many did it **hold**, what did it **share**? The result itself was
never counted as the measure in any lesson, because the counts of two notations
giving the same result diverged every time.

| Lesson | Form measured | Created / held / shared |
|---|---|---|
| `collections/01` Lists | a thousand-item list and eight growth forms | 1000 created and held; binding a second name creates 0, `[one] * 5` creates 0 and shares one item across five slots |
| `collections/02` Tuples | six operations immutability closes off and `t += (new,)` | 1 created; the container is new, items are shared (`big[0] is tup[0]` → True); multiple return creates 0 |
| `collections/03` Dictionaries | membership on two hundred items and the `keys()` view | 200 comparisons in a list, 1 in a set and dict; the view shares the dict and grows from 2 to 3, a snapshot copy holds 1000 slots |
| `collections/04` Sets | deduplicating three hundred items and six set operations | 15050 comparisons by hand, 200 with a set; six operations create 0 items and every object in the result is shared |
| `collections/05` Specialized Collections | three notations of the same grouping and trimming a thousand-item stream | `setdefault` creates 320, a default dict 100 containers; in the stream all three notations produce 1000, held is 1000 against 5; returning creates 3 intermediate containers with a list, 0 with a deque |
| `collections/06` Slicing | eight slice forms and copying a nested list | all eight create 0 items and return a new container; in a nested list 4 get produced and a slice copy shares both inner lists, a deep copy gives 4 |
| `iteration/01` Iterator Protocol | the `iter` and `next` contract, three traversal forms | `iter` writes 0 references, `list()` writes 3, a comprehension creates 3 new objects; the first two share items, the last shares 0 |
| `iteration/02` Generators | calling a function containing `yield`, and `yield from` | at setup a comprehension creates 1000, a generator 0; at three items it stays at 3, `yield from` stays at 1 up to the first item |
| `iteration/03` Generator Expressions | two notations of summing a thousand items | both create 1000 and give 499500; held is 1000 against 0 in the list |
| `iteration/04` Comprehension Syntax | list, set, dict comprehension, and where the filter condition sits | 1000 / 100 / 100 held, shared 1000 / 0 / 100; filtering before production creates 5, after creates 10 |
| `iteration/05` Lambda and Higher-Order Functions | how many times the key function is called, and `map` with `filter` | key called once per item, 200 calls total; a hand-written sort makes 39800; `sorted` builds a new container and shares 200 items, `map` creates 0 at setup |
| `iteration/06` Decorators | the layers wrapping builds and preserving metadata | unwrapped 1, one decorator 2, two decorators 3 layers; the object bound to the name changes, the original function is shared (`single.__wrapped__ is compute` → True); a memoizing wrapper reduces 20 requests to 5 calls and holds 5 items |
| `modules-and-packages/01` The Module System | three import requests and search path order | 2 module objects created, 1 held; two names share the same object and the shared log module stays at 1 |
| `modules-and-packages/02` Package Structure | the object relative and absolute notation resolve to | a single request runs 5 bodies and holds 5 objects; relative and absolute share 0 (`is` → False) |
| `modules-and-packages/03` A Tour of the Standard Library | the same three jobs written with the library and by hand | by hand creates 800 and holds 800, with the library creates 3 and holds 0; the result is 3 in both |
| `modules-and-packages/04` Reloadability and the Main Block | import, script run, and reload | a script run creates a 2nd module object, the first is kept, shared definitions 0; reload shares the module, renews the function |

Four readings cut across the table. **Laziness does not reduce production, it
reduces holding:** in summing a thousand items, both notations produce 1000
objects and both give 499500; the only place they part is what is held in the
list. **A copy is one layer deep:** every operation that builds a new container
renews the outer container and shares what is inside it — in tuples, in slicing,
in set operations alike. **What decides the cost is not the data but the form of
access:** the same two hundred items ask for 200 comparisons in a list, 1 in a
set. **A module is an object too, and it gets created once:** the last topic
carried the same counting scheme past the file boundary and measured that import
is an act of creation too.

Throughout the course, the containers stood ready-made. List, tuple, dict, set,
generator, and module — all were objects the language gave, and what was measured
was the cost of **choosing** them. The next course stands on the other side of
this boundary. **Choosing a container and writing a container are different
things:** an object having a length, being comparable, and being able to serve as
a key in a container is not a chosen trait, it is a contract that gets
**written**. The Object-Oriented Python and Types course builds the side that
writes that contract: classes that fit the language's syntax, situations where
composition is chosen over inheritance, and contracts made explicit with type
hints. Here, how many objects each container cost was counted; there, the object
that will stand in its place will itself be written.
