GeneratorExit

close() throws it into the generator at the paused yield — let it propagate (or return), never yield again.

InheritsBaseException›GeneratorExit
Control-flow exceptionPython 3 (all)Live demo
GeneratorExit(*args)
Raised by
gen.close(), garbage collection of a suspended generator, coroutine.close()
Message
empty — you normally never see it
Quick fix
cleanup in finally; after GeneratorExit, return or re-raise
Watch out
yielding after it → RuntimeError: generator ignored GeneratorExit

Demo

Live evaluation
Advance the generator n times, then close() it. GeneratorExit appears at the paused yield — if there is one.
Try:
Inputs
nintnext() calls before close()
Code
log = []
def reader():
    try:
        while True:
            yield 'line'
    except GeneratorExit:
        log.append('GeneratorExit received')
        raise
g = reader()
for _ in range(2):
    next(g)
g.close()
log
Result
['GeneratorExit received']

With n = 0 the log stays empty: a generator that never started has no paused yield to throw into, so close() just marks it finished. Once started, close() raises GeneratorExit at the yield; the handler re-raises it and close() returns normally. In Handle, returning instead of re-raising is also allowed — and in 3.13+ close() gives you that value.

Constructor

NameTypeRequiredDescription
*argsobjectnoUnused in practice; close() throws it without arguments.

Attributes

AttributeTypeMeaning
argstupleEmpty when thrown by close().
__traceback__traceback | NonePoints at the yield where the generator was paused.

Common patterns

Cleanup belongs in finally
finally runs on close(), on exhaustion and on errors — except GeneratorExit only on close().
def read_rows(path):
    f = open(path)
    try:
        for line in f:
            yield line.rstrip('\n')
    finally:
        f.close()
Close deterministically
Do not rely on garbage collection to close a half-consumed generator — contextlib.closing calls close() on exit.
from contextlib import closing
with closing(read_rows('data.csv')) as rows:
    header = next(rows)
Return a result when closed (3.13+)
A generator used as a sink can return its summary; close() returns it.
def averager():
    total = count = 0
    try:
        while True:
            total += yield
            count += 1
    except GeneratorExit:
        return total / count if count else None

Examples

1. Not an Exception subclass
issubclass(GeneratorExit, Exception)
Returns
False
2. close() runs finally
log = [] def gen(): try: yield 1 yield 2 finally: log.append('cleanup') g = gen() next(g) g.close() log
Returns
['cleanup']
3. except Exception does not intercept it
def gen(): while True: try: yield except Exception: pass g = gen() next(g) g.close() 'closed cleanly'
Returns
'closed cleanly'
4. Closing an unstarted generator runs nothing
log = [] def gen(): try: yield 1 finally: log.append('cleanup') g = gen() g.close() log
Returns
[]
5. close() returns the return value (3.13+)
def gen(): try: yield except GeneratorExit: return 'cleanup done' g = gen() next(g) g.close()
Returns
'cleanup done'
6. break closes it (CPython, when freed)
log = [] def gen(): try: yield from range(10) finally: log.append('closed') for x in gen(): if x == 2: break log
Returns
['closed']
7. close() on a finished generator is a no-op
def gen(): yield 1 g = gen() list(g) g.close() 'ok'
Returns
'ok'

Pitfalls

1. Swallowing GeneratorExit and yielding again
If the generator catches GeneratorExit and reaches another yield, close() raises RuntimeError. Return (or re-raise) instead.
pass and loop
def ticker():
    while True:
        try:
            yield 'tick'
        except GeneratorExit:
            pass
g = ticker()
next(g)
g.close()
RuntimeError: generator ignored GeneratorExit
return
def ticker():
    while True:
        try:
            yield 'tick'
        except GeneratorExit:
            return
g = ticker()
next(g)
g.close()
'closed'
'closed'
2. A bare except inside a generator
except: catches GeneratorExit too, so the loop carries on to the next yield. except Exception lets it through.
bare except
def safe_items(items):
    for x in items:
        try:
            yield int(x)
        except:
            continue
g = safe_items(['1', '2', '3'])
next(g)
g.close()
RuntimeError: generator ignored GeneratorExit
except Exception
def safe_items(items):
    for x in items:
        try:
            yield int(x)
        except Exception:
            continue
g = safe_items(['1', '2', '3'])
next(g)
g.close()
'closed'
'closed'
3. Cleanup only in except GeneratorExit
That handler runs only on close(). When the generator is consumed to the end, the cleanup never happens.
except GeneratorExit
log = []
def gen():
    try:
        yield 1
    except GeneratorExit:
        log.append('cleanup')
        raise
list(gen())
log
[]
finally
log = []
def gen():
    try:
        yield 1
    finally:
        log.append('cleanup')
list(gen())
log
['cleanup']

When to use

Use it
  • Knowing why finally blocks in generators run when a loop breaks early
  • except GeneratorExit: return result — a sink generator that reports on close (3.13+)
  • Debugging RuntimeError: generator ignored GeneratorExit
Reach for something else
  • General cleanup → try/finally or a with block inside the generator
  • Raising it yourself → call gen.close() instead
  • Catching it in a bare except → catch Exception

Notes

CPython impl
Objects/genobject.c — gen_close() throws GeneratorExit; if the generator yields again it raises RuntimeError("generator ignored GeneratorExit")
Why BaseException
The docs: it is technically not an error, so except Exception in generator code must not swallow it
Garbage collection
A suspended generator that is garbage-collected is closed; in CPython that happens as soon as the last reference goes away
Coroutines
coroutine.close() and async generator aclose() use the same mechanism

FAQ

The generator caught GeneratorExit (often via a bare except: or except BaseException:) and then yielded another value. A closing generator must finish: re-raise the GeneratorExit, return, or let it propagate. Replace the bare except with except Exception.

History

3.13
If a generator returns a value upon being closed, the value is returned by close().