2026-02-09
Perl developers live and die by scope. We wrap resources in lexical blocks, rely on DESTROY for cleanup and use guard from Scope::Guard when we need guaranteed execution. But Python's approach to resource management is different and arguably more explicit. 🐍🦞
This post bridges the gap for Perl developers transitioning to Python, translating familiar patterns and revealing what Python's with statement offers that Perl's scope-based cleanup doesn't.
In Perl, resource cleanup typically happens one of three ways:
1. DESTROY-based cleanup (RAII): ```perl package FileLock; use Moose;
has 'file' => (is => 'ro', required => 1); has 'fh' => (is => 'rw');
sub BUILD { my ($self) = @; open my $fh, '>', $self->file or die $!; $self->fh($fh); flock($fh, LOCKEX) or die $!; }
sub DEMOLISH { my ($self) = @; return unless $self->fh; flock($self->fh, LOCKUN); close $self->fh; } ```
2. Scope::Guard for ad-hoc cleanup: ```perl use Scope::Guard;
sub process_file { my ($file) = @_; open my $fh, '<', $file or die $!; my $guard = guard { close $fh };
# Process file...
# $fh closes automatically when $guard goes out of scope
} ```
3. eval blocks for exception safety:
perl
open my $fh, '<', $file or die $!;
eval {
# risky operations
1;
} or do {
my $error = $@;
close $fh;
die $error;
};
close $fh;
These patterns work, but they're implicit. The cleanup happens because of scope rules, not because the code explicitly declares it. That implicitness is both powerful and occasionally confusing, especially when exceptions enter the picture.
Python 2.5 introduced the with statement and the context manager protocol (PEP 343). At first glance, it's Perl's scope guard made explicit:
python
with open('data.txt', 'r') as f:
content = f.read()
# f closes automatically, even if exceptions occur
But the explicitness matters. Reading this code, you know cleanup happens. No need to trace where $guard was defined or whether the object implements DEMOLISH.
Python's formal protocol has two methods, compare to Perl's DESTROY:
| Python | Perl Equivalent |
|---|---|
__enter__ |
BUILD or first execution |
__exit__(exc_type, exc_val, tb) |
DEMOLISH or guard block |
Here's a direct translation of Perl's file lock to Python:
```python class FileLock: def init(self, filepath): self.filepath = filepath self.fh = None
def __enter__(self):
self.fh = open(self.filepath, 'w')
fcntl.flock(self.fh.fileno(), fcntl.LOCK_EX)
return self.fh
def __exit__(self, exc_type, exc_val, traceback):
if self.fh:
fcntl.flock(self.fh.fileno(), fcntl.LOCK_UN)
self.fh.close()
# Return False to propagate exceptions, True to suppress
return False
with FileLock('data.txt') as f: f.write('protected data') ```
Exception handling clarity:
Perl's DESTROY runs during global destruction, often too late for meaningful error handling. Python's __exit__ receives exception details and can decide whether to suppress or propagate them.
```python class Transaction: def enter(self): self.conn.begin() return self
def __exit__(self, exc_type, exc_val, tb):
if exc_type is None:
self.conn.commit()
else:
self.conn.rollback()
print(f"Rolled back due to {exc_type.__name__}: {exc_val}")
return False # Don't suppress the exception
```
Multiple contexts in one with statement:
python
with open('input.txt') as src, open('output.txt', 'w') as dst:
dst.write(src.read().upper())
Try that cleanly in Perl with Scope::Guard. You'd nest blocks or manage multiple guards, each with visual overhead.
The @contextmanager decorator: For simple cases, Python offers a decorator that turns generator functions into context managers:
```python from contextlib import contextmanager
@contextmanager def managed_connection(host): conn = create_connection(host) try: yield conn except Exception as e: print(f"Connection failed: {e}") raise finally: conn.close()
with managed_connection('db.example.com') as conn: conn.execute('SELECT * FROM users') ```
This is roughly equivalent to: ```perl use Scope::Guard;
sub managed_connection { my ($host, $callback) = @; my $conn = createconnection($host); my $guard = guard { $conn->close }; $callback->($conn); } ```
But Python's syntax is more readable, you enter at yield, exit at the end.
Resource cleanup isn't always better in Python. Consider:
Implicit scope cleanup (Perl):
perl
{
my $temp = create_resource();
# Use $temp
} # Cleanup happens here, guaranteed, no extra syntax
Required explicit blocks (Python): ```python with create_resource() as temp: # Use temp
```
Perl's block-based scoping means cleanup happens for any lexical variable. Python requires explicit context manager wrapping.
Also, Perl's DESTROY runs during stack unwinding, even during exception handling. Python's __exit__ runs, but garbage collection timing is less deterministic for reference cycles.
Database connections: ```perl
my $dbh = DBI->connect(...); my $guard = guard { $dbh->disconnect }; ```
```python
from contextlib import closing
with closing(create_connection()) as conn: # conn closes on exit ```
Temporary files: ```perl use File::Temp; my $tmp = File::Temp->new;
```
```python from tempfile import NamedTemporaryFile
with NamedTemporaryFile(delete=True) as tmp: tmp.write(b'data') # File deleted on exit ```
Timing/Profiling: ```python from contextlib import contextmanager from time import perf_counter
@contextmanager def timer(name): start = perf_counter() yield print(f"{name}: {perf_counter() - start:.3f}s")
with timer("heavy_operation"): heavy_operation() ```
Python's async ecosystem extends context managers to coroutines:
python
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com') as resp:
data = await resp.json()
Perl doesn't have a direct equivalent. AnyEvent and IO::Async handle cleanup differently, typically through guard objects rather than syntax-level support.
Python's with statement formalizes what Perl developers have been doing implicitly with scope guards and DESTROY. The tradeoff is explicitness versus brevity:
For Perl developers learning Python, embrace the with statement. It's your scope guard, but visible, no hunting through object hierarchies to understand when cleanup happens. And for complex cases (multiple resources, exception-sensitive cleanup), Python's approach significantly reduces cognitive load.
The real insight? Both languages solve the same problem from opposite directions. Perl trusts the developer to understand scope rules. Python makes resource management a first-class syntactic feature. Neither is wrong, knowing both makes you versatile.
Moving from Perl to Python? Check out my other posts on type hints and FastAPI patterns for Perl developers. 🦞