This rule raises an issue when a @contextmanager generator performs cleanup after yield without guaranteeing that cleanup
still runs if the generator exits early.
@contextmanager turns a generator into a
context manager. The code before yield runs on enter, the with body runs at the yield, and the code after
yield is meant to clean up on exit.
If cleanup sits after yield without a try/finally (or a nested with), it does not run when:
with body raises an exceptionIn those cases, resources such as files, locks, or connections can stay open.
from contextlib import contextmanager
@contextmanager
def temporary_file(path):
handle = open(path, "w")
yield handle
handle.close() # Noncompliant: skipped if the with-block raises
If statements after yield are not cleanup (for example optional logging that is safe to skip), or cleanup is already guaranteed by a
nested with or an enclosing try/finally the analyzer failed to see, treat that as a false positive. Marking a
real resource release as won’t-fix is wrong: exceptions and early generator close can still skip those statements and leak the resource.
Missed cleanup can leak resources, leave locks held, or leave shared state inconsistent. These failures often show up only under exceptions or early generator shutdown, which makes them harder to reproduce.
Wrap the yield in try/finally so cleanup always runs, or nest resource acquisition in a with
statement so the inner context manager owns the cleanup.
from contextlib import contextmanager
@contextmanager
def temporary_file(path):
handle = open(path, "w")
yield handle
handle.close() # Noncompliant: cleanup after "yield" may be skipped
from contextlib import contextmanager
@contextmanager
def temporary_file(path):
handle = open(path, "w")
try:
yield handle
finally:
handle.close()
from contextlib import contextmanager
@contextmanager
def locked_section(lock):
lock.acquire()
yield
lock.release() # Noncompliant: "release" is skipped on exception
from contextlib import contextmanager
@contextmanager
def locked_section(lock):
with lock:
yield
When an exception escapes the with body, Python resumes the generator at yield and re-raises there. A
finally block still runs in that situation, and so does __exit__ for a nested with. Plain statements after
yield do not.
contextlib.contextmanager