Day 18 – Context Managers
📦 Day 18 – Context Managers
1. Learning Objectives
By the end of Day 18, you will be able to:
- Understand what context managers are and why they simplify resource management in AEC scripts.
- Use the
withstatement for files, database connections, and temporary settings. - Create your own context managers using
__enter__and__exit__methods. - Use
contextlibutilities (@contextmanagerdecorator) for simpler creation. - Apply context managers to practical AEC tasks: safely opening model files, temporary unit conversion context, automatic timing of analysis runs, and saving/restoring plot settings.
2. Concept Explanation
2.1 Why Context Managers in AEC?
In AEC programming, you often work with resources that need careful setup and teardown:
- Files: Open a CSV → read data → close the file. If something crashes mid‑read, the file must still close.
- Plotting: Set matplotlib style → draw → restore original style.
- Temporary settings: Switch to metric units → perform calculations → switch back.
- Database connections: Connect to a material database → query → disconnect.
Context managers automate the "setup → teardown" pattern, ensuring cleanup even if an error occurs.
2.2 The with Statement and Built‑in Context Managers
The most familiar context manager is open():
# Without context manager (error‑prone)
f = open("data.csv", "r")
content = f.read()
f.close() # What if an exception happens before this line?
# With context manager (safe and clean)
with open("data.csv", "r") as f:
content = f.read()
# File is automatically closed, even if an exception occurs
2.3 Creating a Context Manager with a Class
Define a class with __enter__ and __exit__ methods.
class UnitConverter:
"""Context manager that temporarily switches units."""
def __enter__(self):
print("Switching to metric units...")
# Could modify global settings here
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Restoring original units...")
# Clean up even if an error occurred
# Return False to propagate any exception (default is False)
return False
with UnitConverter():
print(" Performing calculations in metric...")
print("Back to project units.")
2.4 Using @contextmanager from contextlib
For simple cases, you can use a generator function with the @contextmanager decorator:
from contextlib import contextmanager
@contextmanager
def temporary_setting(name, value):
"""Temporarily change a setting and restore it afterwards."""
original = getattr(some_module, name) # save original
setattr(some_module, name, value) # apply new value
try:
yield # the code inside `with` block runs here
finally:
setattr(some_module, name, original) # restore original
2.5 Multiple Context Managers
You can nest or combine them in one with:
with open("input.csv", "r") as infile, open("output.csv", "w") as outfile:
data = infile.read()
outfile.write(data.upper())
3. Code Examples
Example 1: Timing context manager for benchmarking
import time
class Timer:
"""Context manager that times the execution of a block."""
def __enter__(self):
self.start = time.time()
return self # so we can access .elapsed after
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.time()
self.elapsed = self.end - self.start
print(f"Block took {self.elapsed:.4f} seconds")
return False # don't suppress exceptions
# Usage
with Timer() as t:
total = sum(i**2 for i in range(10_000_000))
print(f"Sum = {total}, Elapsed = {t.elapsed:.4f}s")
Example 2: Safe CSV reading with error logging context manager
import csv
import sys
class CSVReader:
"""Context manager for robust CSV reading with error logging."""
def __init__(self, filename):
self.filename = filename
self.file = None
def __enter__(self):
try:
self.file = open(self.filename, "r", newline="")
self.reader = csv.DictReader(self.file)
print(f"Opened {self.filename} successfully.")
return self.reader
except FileNotFoundError:
print(f"ERROR: File {self.filename} not found.")
sys.exit(1) # or raise
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
print(f"Closed {self.filename}.")
if exc_type is not None:
print(f"An error occurred: {exc_val}")
return False # re‑raise any exception
# Usage
with CSVReader("beams.csv") as reader:
for row in reader:
span = float(row["Span_m"])
if span > 8.0:
print(f"Long span: {row['Mark']} = {span}m")
Example 3: Temporary matplotlib style context manager
import matplotlib.pyplot as plt
from contextlib import contextmanager
@contextmanager
def plot_style(style="seaborn-v0_8-whitegrid"):
"""Temporarily change matplotlib style."""
original = plt.rcParams.copy()
plt.style.use(style)
try:
yield
finally:
plt.rcParams.update(original)
# Usage
with plot_style("ggplot"):
fig, ax = plt.subplots()
ax.plot([0, 1, 2], [0, 1, 4])
ax.set_title("ggplot style")
plt.show()
# Style is restored automatically
Example 4: Temporary working directory context manager
import os
from contextlib import contextmanager
@contextmanager
def working_directory(path):
"""Temporarily change the current working directory."""
original = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(original)
# Usage
with working_directory("/tmp/project_data"):
# All file operations happen in /tmp/project_data
with open("temp_output.txt", "w") as f:
f.write("Hello from temp directory")
# Now we're back in original directory
Example 5: Context manager for database connection (simulated)
class MaterialDatabase:
"""Simulated connection to a material properties database."""
def __init__(self, db_name):
self.db_name = db_name
self.connected = False
def __enter__(self):
print(f"Connecting to {self.db_name}...")
self.connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Disconnecting from {self.db_name}...")
self.connected = False
if exc_type:
print(f"DB error occurred: {exc_val}")
return False
def query(self, material):
if not self.connected:
raise RuntimeError("Not connected to database")
# Simulated lookup
database = {
"Steel": {"density": 7850, "E": 200000},
"Concrete": {"density": 2400, "E": 30000},
}
return database.get(material, {"density": 0, "E": 0})
# Usage
with MaterialDatabase("AISC_Steel_DB") as db:
steel_props = db.query("Steel")
print(f"Steel density: {steel_props['density']} kg/m³")
# Connection automatically closed
4. Hands‑on Exercises (3–5 Problems)
Problem 1 – File write with automatic closure
Write a simple script that uses with open(...) to write three lines of text to "report.txt". Then read the file back (also with with) and print its contents.
Problem 2 – Timer context manager
Create a context manager Timer (using a class or @contextmanager) that measures the time of the enclosed block. Use it to time a loop that computes the sum of squares from 1 to 1,000,000.
Problem 3 – Temporary precision change
Imagine a global variable PRECISION = 2. Write a context manager temporary_precision(n) that temporarily changes PRECISION to n inside the with block, then restores it. Demonstrate by printing a formatted number inside and outside the block.
Problem 4 – Safe plot export
Write a context manager save_plot(filename) that:
- On entry: creates a new matplotlib figure.
- On exit: saves the figure to
filenameand closes it (usingplt.close()). - Handles errors during save gracefully.
Test by plotting a simple line and saving as "test_plot.png".
Solutions (attempt first):
# P1
with open("report.txt", "w") as f:
f.write("Beam analysis report\n")
f.write("Span: 6.0 m\n")
f.write("Load: 25 kN/m\n")
with open("report.txt", "r") as f:
print(f.read())
# P2
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
self.end = time.time()
self.elapsed = self.end - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False
with Timer():
total = sum(x**2 for x in range(1_000_001))
print(f"Sum of squares: {total}")
# P3
PRECISION = 2
from contextlib import contextmanager
@contextmanager
def temporary_precision(n):
global PRECISION
original = PRECISION
PRECISION = n
try:
yield
finally:
PRECISION = original
value = 3.14159
print(f"Outside: {value:.{PRECISION}f}") # 3.14
with temporary_precision(4):
print(f"Inside: {value:.{PRECISION}f}") # 3.1416
print(f"Outside again: {value:.{PRECISION}f}") # 3.14
# P4
import matplotlib.pyplot as plt
class save_plot:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.fig, self.ax = plt.subplots()
return self.ax
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.fig.savefig(self.filename, dpi=150)
print(f"Saved plot to {self.filename}")
else:
print(f"Error during plotting: {exc_val}")
plt.close(self.fig)
return False
with save_plot("test_plot.png") as ax:
ax.plot([0,1,2], [0,1,4], 'b-o')
ax.set_title("Test")
5. Applied Challenge Task
Task: Robust Model Export Context Manager
Design a context manager model_export that manages the entire workflow of exporting a structural model to a CSV file for CAD import.
The context manager should:
On entry (
__enter__):- Ask the user for an export filename (or accept as argument).
- Open the file for writing.
- Write a header row:
"ELEMENT_TYPE,ID,LEVEL,X_START,Y_START,X_END,Y_END,MATERIAL" - Print a message indicating export started.
- Return a writer object (or a custom handler).
Inside the
withblock, the user can call a methodwrite_element(etype, eid, level, x1, y1, x2, y2, material)that writes one row.On exit (
__exit__):- Ensure the file is properly closed.
- Print a summary of how many elements were exported.
- If an exception occurred during export (e.g., bad data), catch it, log the error, and optionally save what was written so far (do not delete the file).
Bonus:
Add a nested context manager for temporary unit conversion: inside the with model_export(...) block, use another context manager metric_units that temporarily sets a global UNITS = "metric" flag. The write_element method should check this flag to decide if coordinates need conversion.
Why this matters:
Context managers are ideal for workflows with a clear setup/teardown pattern: opening databases, exporting reports, batch processing files. This task models a real BIM‑CAD export pipeline.
6. Brief Review Summary
- Context managers automate resource setup and teardown using
withblocks. - Built‑in example:
with open(...) as f:– file auto‑closes. - Create custom managers: class with
__enter__and__exit__, or@contextmanagerdecorator on a generator. __exit__receives exception info – you can handle, log, or suppress errors.- Multiple context managers can be combined in one
withstatement. - Use cases in AEC: file I/O, database connections, temporary settings, timing, plotting.
Key takeaway:
Context managers make your code safer and more readable by ensuring cleanup always happens – critical when dealing with files, databases, or graphics in engineering automation.
7. Preview of Next Topic (Day 19)
Tomorrow we dive into BIM Data with IfcOpenShell.
You’ll learn:
- What IFC (Industry Foundation Classes) is and why it matters for interoperability.
- How to install and use
IfcOpenShellto read.ifcfiles. - Extracting building elements (walls, slabs, columns) and their properties.
- Querying spatial structure (site, building, storey).
- A practical exercise: read an IFC file, list all walls on a given floor, and compute total wall volume.
This is your first step into real BIM data – a critical skill for modern AEC practice.

Comments
Post a Comment