Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions exercises/foundations/06_scopes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Exercise 06 - Scope Station: Reactor Rescue (Scope and Namespaces)

## Description
Orbital Station Alpha is failing! Engineers rushed the code and left variables in the wrong places: some are trapped inside functions, some are missing the right keyword, and some are hiding Python's own tools.

This is a **debugging game**. `scopes.py` is already written, but it is *almost* working. Your job in each level is to figure out **where each variable must live** (and which keyword, if any, it needs) so the system runs.

Remember the question from the lesson: *"Where does this name live, and who needs to see it?"*

> Do not rename the functions. The tests call them by name. You decide where the variables go.

---

## Levels

### Level 1 - Power Grid (Local vs. Global)
`show_banner()` crashes with a `NameError`. The station name is created inside `boot_system()`, but two functions need it.
* **Your job**: make both `boot_system()` and `show_banner()` work.
* **Expected**: `show_banner()` returns `"=== Orbital Station Alpha ==="`.

> **Next step:** Run `python check.py 06`. Once Level 1 passes, remove the `@pytest.mark.skip` line above `test_level2` in `test_scopes.py`.

### Level 2 - Life Support (Enclosing Scope)
`build_oxygen_monitor()` returns an inner function `read_level()`, but it can't find the oxygen value.
* **Your job**: put `oxygen` where `read_level()` can find it by looking outward, and don't use a global.
* **Expected**: the monitor returns `"Oxygen at 100%"`.

> **Next step:** Unlock `test_level3`.

### Level 3 - Comms Array (Built-in Scope)
`summarize_signals()` fails with `TypeError: ... object is not callable`. Something is hiding Python's built-in tools.
* **Your job**: find what is shadowing the built-ins and fix it.
* **Expected**: `"3 signals, strength 49"`.

> **Next step:** Unlock `test_level4`.

### Level 4 - Reactor Core (`UnboundLocalError`)
`add_bonus_power()` crashes with `UnboundLocalError`.
* **Your job**: fix it by sending the value **in** with a parameter and sending the result **out** with `return`. The global `power` must stay `50`, and `global` is not allowed in this level.
* **Expected**: `add_bonus_power(50)` returns `60`.

> **Next step:** Unlock `test_level5`.

### Level 5 - Cargo Bay (Mutable Objects)
`load_crate()` adds items to a list that vanishes when the function ends, and `get_manifest()` can't find it.
* **Your job**: make both functions share the same list. You don't need `global`, because you only change the list's contents.
* **Expected**: loading `"Titanium"` then `"Ice"` gives a manifest ending in `["Titanium", "Ice"]`.

> **Next step:** Unlock `test_level6`.

### Level 6 - Alarm System (`global`)
`trigger_alarm()` should count alarms in the global `alarm_count`, but crashes.
* **Your job**: let the function rebind the global variable.
* **Expected**: three calls return `1`, `2`, `3` (from a fresh start) and `alarm_count` ends up updated.

> **Next step:** Unlock `test_level7`.

### Level 7 - Drone Memory (`nonlocal`)
Each drone made by `make_drone_counter()` should remember how many scans it did, but `scan()` crashes.
* **Your job**: let the inner function update the enclosing variable.
* **Expected**: one drone counts `1, 2, 3`, and a second drone starts again at `1`.

> **Next step:** Unlock `test_level8`.

### Level 8 - Diagnostics (`globals()` and `locals()`)
Complete the three helper functions in the `LEVEL 8` section:
* `list_local_names(a, b)`: return a **sorted list** of the names in the function's local namespace.
* `is_global(name)`: return `True` if `name` exists in the global namespace.
* `probe_hidden()`: try to read `secret_code` (it only exists inside `_make_secret()`). Return `True` if Python raises `NameError`.

> **Next step:** Unlock `test_level9`.

### Level 9 - BOSS: Reactor Boot Sequence
`run_station()` has **three** different scope bugs at once. Use everything you have learned.
* **Expected**: `"Checks: 3 | Passed: 2 | Peak: 91"`.

> **Next step:** If Level 9 passes, congratulations! You saved the station.

---

## How to Run the Tests
Run the following command from the repository root:
```bash
python check.py 06
```

Or run directly with pytest:
```bash
pytest exercises/foundations/06_scopes/test_scopes.py -v
```

---

## Resources & Notes
Review the concepts covered in this exercise in:
* [The Python Ledger Curriculum: Scope and Namespaces](https://thepythonledger.github.io/Docusaurus-engine/lessons/code-organization/scope-and-namespaces)
121 changes: 121 additions & 0 deletions exercises/foundations/06_scopes/scopes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# ORBITAL STATION ALPHA - Reactor Rescue
# Every system below is almost working. Find WHERE each variable should live.
# Do not rename the functions - the tests call them by name.


# LEVEL 1 - Power Grid
def boot_system():
STATION_NAME = "Orbital Station Alpha"
return f"Booting {STATION_NAME}..."


def show_banner():
return f"=== {STATION_NAME} ==="


# LEVEL 2 - Life Support
def calibrate_sensors():
oxygen = 100
return "Sensors calibrated"


def build_oxygen_monitor():
def read_level():
return f"Oxygen at {oxygen}%"

return read_level


# LEVEL 3 - Comms Array
list = [12, 7, 30] # incoming signal strengths
sum = 0 # total strength


def summarize_signals():
return f"{len(list)} signals, strength {sum(list)}"


# LEVEL 4 - Reactor Core
power = 50


def add_bonus_power():
print("Current power:", power)
power = power + 10
return power


# LEVEL 5 - Cargo Bay
def load_crate(item):
cargo = []
cargo.append(item)


def get_manifest():
return cargo


# LEVEL 6 - Alarm System
alarm_count = 0


def trigger_alarm():
alarm_count = alarm_count + 1
return alarm_count


# LEVEL 7 - Drone Memory
def make_drone_counter():
scans = 0

def scan():
scans = scans + 1
return scans

return scan


# LEVEL 8 - Diagnostics
def _make_secret():
secret_code = 42
return "secret stored"


def list_local_names(a, b):
c = a + b
# Return a sorted list of the names in THIS function's local namespace.
pass


def is_global(name):
# Return True if `name` exists in the global namespace.
pass


def probe_hidden():
_make_secret()
# Try to read `secret_code` here. Return True if Python raises NameError
# (it is hidden inside _make_secret), otherwise return False.
pass


# LEVEL 9 - BOSS: Reactor Boot Sequence
def configure_reactor():
REACTOR_TARGET = 80
return "Reactor configured"


readings = [72, 91, 85]
max = 0 # highest reading seen so far


def run_station():
checks = 0

def run_check(reading):
checks = checks + 1
return reading >= REACTOR_TARGET

results = [run_check(r) for r in readings]
highest = max(readings)
return f"Checks: {checks} | Passed: {results.count(True)} | Peak: {highest}"
123 changes: 123 additions & 0 deletions exercises/foundations/06_scopes/solution/scopes_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ORBITAL STATION ALPHA - Reactor Rescue (reference solution)

# LEVEL 1 - Power Grid: shared by two functions -> module level
STATION_NAME = "Orbital Station Alpha"


def boot_system():
return f"Booting {STATION_NAME}..."


def show_banner():
return f"=== {STATION_NAME} ==="


# LEVEL 2 - Life Support: lives in the enclosing function
def calibrate_sensors():
return "Sensors calibrated"


def build_oxygen_monitor():
oxygen = 100

def read_level():
return f"Oxygen at {oxygen}%"

return read_level


# LEVEL 3 - Comms Array: don't shadow built-ins
signals = [12, 7, 30]


def summarize_signals():
return f"{len(signals)} signals, strength {sum(signals)}"


# LEVEL 4 - Reactor Core: parameter + return instead of global
power = 50


def add_bonus_power(current_power):
print("Current power:", current_power)
return current_power + 10


# LEVEL 5 - Cargo Bay: shared mutable list, no `global` needed
cargo = []


def load_crate(item):
cargo.append(item)


def get_manifest():
return cargo


# LEVEL 6 - Alarm System: rebinding a global needs `global`
alarm_count = 0


def trigger_alarm():
global alarm_count
alarm_count = alarm_count + 1
return alarm_count


# LEVEL 7 - Drone Memory: rebinding an enclosing variable needs `nonlocal`
def make_drone_counter():
scans = 0

def scan():
nonlocal scans
scans = scans + 1
return scans

return scan


# LEVEL 8 - Diagnostics
def _make_secret():
secret_code = 42
return "secret stored"


def list_local_names(a, b):
c = a + b
return sorted(locals().keys())


def is_global(name):
return name in globals()


def probe_hidden():
_make_secret()
try:
secret_code
except NameError:
return True
return False


# LEVEL 9 - BOSS: Reactor Boot Sequence
REACTOR_TARGET = 80
readings = [72, 91, 85]


def configure_reactor():
return "Reactor configured"


def run_station():
checks = 0

def run_check(reading):
nonlocal checks
checks = checks + 1
return reading >= REACTOR_TARGET

results = [run_check(r) for r in readings]
highest = max(readings)
return f"Checks: {checks} | Passed: {results.count(True)} | Peak: {highest}"
Loading