# A puzzle about the StopIteration exception

**URL:** <https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705>\
**Category:** Python Help\
**Created:** [August 23, 2026, 9:15am UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705 "2026-08-23T09:15:50Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 9:15am UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/1 "2026-08-23T09:15:50Z")

</div>

This category is for help and general discussion. This is a general discussion topic.

I found the interface to the StopIteration exception a bit puzzling. I think I’ve solved the puzzle now. Perhaps some of you would like to look at it, and try to solve it yourself. I’d post the solution in about a week or so.

The puzzle is also available at [Python's StopIteration exception puzzled me. Here's why. What's going on here? BTW, I've solved the puzzle. · GitHub](https://gist.github.com/jfine2358/ba867ca914b48917cb6b018f05725b63).

Here’s the puzzle. What’s going on here? Extra credit for ideas about removing this puzzle from Python.

```python-repl
>>> def doit(*args):
...
... exc = StopIteration(*args)
... return f"{exc.value=}, {exc.args=}"

>>> doit()
'exc.value=None, exc.args=()'

>>> doit(None)
'exc.value=None, exc.args=(None,)'

>>> doit(1)
'exc.value=1, exc.args=(1,)'

>>> doit(1, 2)
'exc.value=1, exc.args=(1, 2)'

>>> doit(1, 2, 3)
'exc.value=1, exc.args=(1, 2, 3)'

>>> doit((1, 2, 3))
'exc.value=(1, 2, 3), exc.args=((1, 2, 3),)'

>>> doit((1, 2), 3)
'exc.value=(1, 2), exc.args=((1, 2), 3)'

```

---

<div class="post-metadata">

**Author:** ![jonathandung](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jonathandung/32/35459_2.png) [@jonathandung](https://discuss.python.org/u/jonathandung)\
**Post date:** [August 23, 2026, 10:40am UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/2 "2026-08-23T10:40:24Z")

</div>

I don’t think this is puzzling at all. The `args` attributes is defined on `BaseException` and stores the positional arguments passed to the constructor as a tuple. The `value` attribute on `StopIteration` gives the first argument, which is the first item of said tuple.

---

<div class="post-metadata">

**Author:** ![warden](https://avatars.discourse-cdn.com/v4/letter/w/13edae/32.png) [@warden](https://discuss.python.org/u/warden)\
**Post date:** [August 23, 2026, 10:49am UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/3 "2026-08-23T10:49:10Z")

</div>

In your example `*args` is obscuring what is going on. What you are observing is:

```python
StopIteration().value # None
StopIteration(None).value # None
StopIteration(1,2).value # 1
StopIteration((1,2,3)).value # (1, 2, 3)

```

The first argument passed to `StopIteration` becomes the value, if omitted the value is `None`, which is vaguely similar to bare `return` or implicit return.

Now why `StopIteration` can take more than one argument is not immediately clear. The reasons are historical. The `*args`, `**kwargs` signature is simply inherited from Exception. Version 3.3 added the value attribute to access the first argument to facilitate the use in exception based control flow for coroutines.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 23, 2026, 10:53am UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/4 "2026-08-23T10:53:44Z")

</div>

You’re constructing an exception. I’m not sure why, since the normal way to construct StopIteration with a value is to return a value from a generator; but like most exceptions, it has a specific collection of named arguments (in this case just one, `value`), and also collects all its args into `args`.

If you don’t understand these things, check the documentation; specifically, since it’s the `args` attribute that seems to cause confusion here (the `value` is simply given a default if one isn’t passed), try this:

> **[Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#BaseException.args)**
>
> The following exceptions are used mostly as base classes for other exceptions. | In Python, all exceptions must be instances of a class that derives from BaseException. In a try statement with an except clause that mentions a particular class, that...

Note that StopIteration is far from unique in this. Other exception types also capture some specific arguments.

> [@jfine2358](#):
>
> Extra credit for ideas about removing this puzzle from Python.

Why?

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 12:22pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/5 "2026-08-23T12:22:14Z")

</div>

Hi Jonathan

Your statement, that exc.value is args[0], is almost correct. However, it does not explain:

> [@jfine2358](#):
>
> ```python
> >>> doit()
> 'exc.value=None, exc.args=()'
> 
> ```

If your statement was correct, we’d get an IndexError. Here’s a challenge that might help you see how puzzling I find StopIteration.

The challenge is to write a function aaa() that on all reasonable inputs gives the same output as doit(). You’ll then see that the behaviour of doit() is a bit peculiar.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 12:29pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/6 "2026-08-23T12:29:39Z")

</div>

Hi

> [@warden](#):
>
> Now why `StopIteration` can take more than one argument is not immediately clear. The reasons are historical.

This is good. Of course, the reasons for any past event are historical. Whereas present events arise from our own free will. (Which might not be as free as we suppose.)

Here’s a related question. What behaviour would an experienced but not advance programmer expect from `StopIteration(1, 2)`. Would such a programmer expect `StopIteration(1, 2, 3)` to give a difference outcome.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 12:34pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/7 "2026-08-23T12:34:35Z")

</div>

Hi Chris

> [@Rosuav](#):
>
> normal way to construct StopIteration with a value is to return a value from a generator

Warden’s statement here is important.

> [@warden](#):
>
> Version 3.3 added the value attribute to access the first argument to facilitate the use in exception based control flow for coroutines.

I think a common way to construction a non-coroutine awaitable is to explicitly constuct a StopIteration exception. It may even be the normal way in this context. I encountered this problem exactly when I was constructing my own non-coroutine awaitables.

---

<div class="post-metadata">

**Author:** ![warden](https://avatars.discourse-cdn.com/v4/letter/w/13edae/32.png) [@warden](https://discuss.python.org/u/warden)\
**Post date:** [August 23, 2026, 12:36pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/8 "2026-08-23T12:36:03Z")

</div>

> [@jfine2358](#):
>
> The challenge is to write a function aaa() that on all reasonable inputs gives the same output as doit(). You’ll then see that the behaviour of doit() is a bit peculiar.

```python
def aaa(*args):
    value = args[0] if args else None
    return f"{value=}, {args=}"

```

> [@jfine2358](#):
>
> Here’s a related question. What behaviour would an experienced but not advance programmer expect from `StopIteration(1, 2)`. Would such a programmer expect `StopIteration(1, 2, 3)` to give a difference outcome.

I would’ve expected `TypeError: StopIteration expects exactly 1 positional argument`, without the historical context and my experience with other built-in exceptions.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 4:37pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/9 "2026-08-23T16:37:09Z")

</div>

> [@warden](#):
>
> `value = args[0] if args else None`

Nice. You’ve done it in one line, and without resorting to obscure tricks. How about:

```python
def aaa(value=None, *args):
    return value

```

The gives exactly the right result for `value`. But it’s impossible to modify this to get the `args` as well.

Why? Well these two give the same value, but have different arguments.

```python
aaa() # Use default argument.
aaa(None) # Explicitly give the same value.

```

Now there’s something a bit odd. For `value` we can use the default argument method. But not for `args`.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 5:07pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/10 "2026-08-23T17:07:55Z")

</div>

Here’s something that really puzzled me.I felt I had to know the answer, to a high degree of certainty.

A `StopIteration` exception has a `value` attribute and an `args` attribute. In the context of constructing an awaitable is the tail part of the message send to the event loop? Or just the shared part, the head `args[0]`?

There are at least three ways to answer this questions.

1. Pythonic intuition.
2. Documentation, perhaps including examples.
3. Running test code.

Now we have:

```python-repl
>>> x_12345 = StopIteration(1, 2, 3, 4, 5)
>>> 
>>> x_12345
StopIteration(1, 2, 3, 4, 5)

```

This suggests to my Pythonic intuition that the tail `2, 3, 4, ,5` is in some sense important. And I’ve not found documentation clearly saying otherwise. So I’m looking for test code, as the true way to resolve my doubts about what the interpreter actually does. (Disclaimer. I’ve not read the PEPs. These might answer this question.)

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 23, 2026, 5:15pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/11 "2026-08-23T17:15:27Z")

</div>

> [@jfine2358](#):
>
> Now we have:
> 
> ```python
> >>> x_12345 = StopIteration(1, 2, 3, 4, 5)
> >>> 
> >>> x_12345
> StopIteration(1, 2, 3, 4, 5)
> 
> ```
> 
> This suggests to my Pythonic intuition that the tail `2, 3, 4, ,5` is in some sense important.

They’re important precisely because you constructed the exception with them. Nobody else will know what to do with them. You can do this with **any** exception.

```python
>>> Exception("spam", "ham", "eggs")
Exception('spam', 'ham', 'eggs')

```

---

<div class="post-metadata">

**Author:** ![warden](https://avatars.discourse-cdn.com/v4/letter/w/13edae/32.png) [@warden](https://discuss.python.org/u/warden)\
**Post date:** [August 23, 2026, 5:24pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/12 "2026-08-23T17:24:26Z")

</div>

The key is realizing that `value` is computed attribute, and not raw input.

> [@Rosuav](#):
>
> You can do this with **any** exception.

`OSError` ate my eggs.

```python
>>> OSError("spam", "ham", "eggs")
OSError('spam', 'ham')

```

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 5:48pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/13 "2026-08-23T17:48:22Z")

</div>

> [@warden](#):
>
> ```python
> >>> OSError("spam", "ham", "eggs")
> OSError('spam', 'ham')
> 
> ```

Well this certainly surprised me. Well done. It even passed my ‘not gone but hidden’ test.

```python-repl
>>> triple = OSError('spam', 'ham', 'eggs')
>>> triple
OSError('spam', 'ham')
>>> triple.args
('spam', 'ham')

```

I’m very impressed.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 23, 2026, 5:48pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/14 "2026-08-23T17:48:25Z")

</div>

> [@warden](#):
>
> `OSError` ate my eggs.
> 
> ```python
> >>> OSError("spam", "ham", "eggs")
> OSError('spam', 'ham')
> 
> ```

It’s even funnier if you start with a number.

```python
>>> type(OSError(1, 2, 3)) is OSError
False

```

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 23, 2026, 5:55pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/15 "2026-08-23T17:55:13Z")

</div>

> [@Rosuav](#):
>
> ```python
> >>> type(OSError(1, 2, 3)) is OSError
> False
> 
> ```

To clarify:

```python
>>> type(OSError(1, 2, 3))
<class 'PermissionError'>

```

I believe this is allowed. It’s in ` __new__ ` that the class is determined, and as I recall it can return something in a different class. I don’t remember what happens to ` __init__ ` in this situation.

What I don’t see, and it may be off-topic for this discussion, why `OSError` does this. However at present this is not something I need to know.

---

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [August 23, 2026, 10:49pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/16 "2026-08-23T22:49:01Z")

</div>

> [@jfine2358](#):
>
> What I don’t see, and it may be off-topic for this discussion, why `OSError` does this

It downcasts to more specific subtypes if the error number is passed in.

The key common element to the whole discussion is that (for convenience in reporting extra information) the default signature of exception constructors is to accept and save arbitrary args. However, subclasses are free to apply more semantic significance than that and when they do, the details will be covered in the docs for the specific exception.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 24, 2026, 3:49pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/17 "2026-08-24T15:49:35Z")

</div>

> [@ncoghlan](#):
>
> The key common element to the whole discussion is that (for convenience in reporting extra information) the default signature of exception constructors is to accept and save arbitrary args.

I agree that the default signature is to accept and save unamed args. I think it is one of several key facts for this discussion. For clarity, keyword arguments are not part of the default.

```python-repl
>>> Exception(1, 2, 3)
Exception(1, 2, 3)
>>> Exception(1, 2, 3, a=4, b=5)
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    Exception(1, 2, 3, a=4, b=5)
    ~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^
TypeError: Exception() takes no keyword arguments

```

> [@ncoghlan](#):
>
> However, subclasses are free to apply more semantic significance than that and when they do, the details will be covered in the docs for the specific exception.

Indeed. And `StopIteration` has a special attribute `value`, in addition to the usual `args`, I’m not sure the semantics of the arguments to `StopIteration` are adequately covered in the docs. Certainly, I did experiments to learn the semantics.

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 24, 2026, 4:04pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/18 "2026-08-24T16:04:13Z")

</div>

Before making the initial post I did some research on the signature of the exceptions in the `builtins` module. More exactly, I use attributes as revealed by `dir`, omitting those that begin with `__` as a proxy for the signature. These investigation as now added to the previous github URL [Python's StopIteration exception puzzled me. Here's why. What's going on here? BTW, I've solved the puzzle. · GitHub](https://gist.github.com/jfine2358/ba867ca914b48917cb6b018f05725b63) .

Here’s what I found. The following exceptions have a unique signature (or more exactly the proxy I described above). One of them is `StopIteration`. All of the signatures have `args`. But `StopIteration` is the only one that has `value`. Should it have both values? And if so, what are their respective functions?

```python
---
1 ('add_note', 'args', 'derive', 'exceptions', 'message', 'split', 'subgroup', 'with_traceback')
   val[:4]=['ExceptionGroup']
---
1 ('add_note', 'args', 'name', 'obj', 'with_traceback')
   val[:4]=['AttributeError']
---
1 ('add_note', 'args', 'value', 'with_traceback')
   val[:4]=['StopIteration']

```

Here are the two most common signatures. Notice that `StopIteration` has the most common (34 exception classes) signature, except for the unique addition of a `value` attribute.

```python
34 ('add_note', 'args', 'with_traceback')
   val[:4]=['Exception', 'ArithmeticError', 'AssertionError', 'BufferError']
---
18 ('add_note', 'args', 'characters_written', 'errno', 'filename', 'filename2', 'strerror', 'with_traceback')
   val[:4]=['OSError', 'BlockingIOError', 'ChildProcessError', 'ConnectionError']

```

Here are the remaining signatures.

```python
---
4 ('add_note', 'args', 'end_lineno', 'end_offset', 'filename', 'lineno', 'msg', 'offset', 'print_file_and_line', 'text', 'with_traceback')
   val[:4]=['SyntaxError', 'IndentationError', '_IncompleteInputError', 'TabError']
---
3 ('add_note', 'args', 'encoding', 'end', 'object', 'reason', 'start', 'with_traceback')
   val[:4]=['UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeTranslateError']
---
2 ('add_note', 'args', 'msg', 'name', 'name_from', 'path', 'with_traceback')
   val[:4]=['ImportError', 'ModuleNotFoundError']
---
2 ('add_note', 'args', 'name', 'with_traceback')
   val[:4]=['NameError', 'UnboundLocalError']

```

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [August 24, 2026, 4:41pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/19 "2026-08-24T16:41:53Z")

</div>

> [@jfine2358](#):
>
> Before making the initial post I did some research on the signature of the exceptions in the `builtins` module. More exactly, I use attributes as revealed by `dir`, omitting those that begin with `__` as a proxy for the signature. These investigation as now added to the previous github URL [Python's StopIteration exception puzzled me. Here's why. What's going on here? BTW, I've solved the puzzle. · GitHub](https://gist.github.com/jfine2358/ba867ca914b48917cb6b018f05725b63) .

But you didn’t think to read the documentation?

> **[Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#BaseException.args)**
>
> The following exceptions are used mostly as base classes for other exceptions. | In Python, all exceptions must be instances of a class that derives from BaseException. In a try statement with an except clause that mentions a particular class, that...

---

<div class="post-metadata">

**Author:** ![jfine2358](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jfine2358/32/36944_2.png) [@jfine2358](https://discuss.python.org/u/jfine2358)\
**Post date:** [August 24, 2026, 5:09pm UTC](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705/20 "2026-08-24T17:09:32Z")

</div>

Hi Chris

> [@Rosuav](#):
>
> But you didn’t think to read the documentation?

I have read the documentation, several times. Particularly that relating to `StopIteration`. That documentation says:

> The exception object has a single attribute `value`, which is given as an argument when constructing the exception, and defaults to [`None`](https://docs.python.org/3/library/constants.html#None "None").

But my initial example shows that there is also an `args` attribute. And `dir` also shows the `args` attribute. In my opinion warden below has given the most helpful response so far to my puzzle.

> [@warden](#):
>
> I would’ve expected `TypeError: StopIteration expects exactly 1 positional argument`, without the historical context and my experience with other built-in exceptions.

If it’s wrong to supply more than one positional argument to `StopIteration`, then I’d prefer that the documentation said so. And also that the Python interpreter raise a `TypeError`.

In case you think the single argument is implied by the documentation, consider the following example, in which what might be called args is silently converted into a single argument, namely a tuple.

```python-repl
>>> d = dict()

>>> d[1]
Traceback (most recent call last):
    d[1]
KeyError: 1

>>> d[1, 2]
Traceback (most recent call last):
    d[1, 2]
KeyError: (1, 2)
>>> 

```

[Next page](https://discuss.python.org/t/a-puzzle-about-the-stopiteration-exception/108705.md?page=2)
