# Improve OSError constructor

**URL:** <https://discuss.python.org/t/improve-oserror-constructor/34390>\
**Category:** Core Development\
**Created:** [September 22, 2023, 11:23am UTC](https://discuss.python.org/t/improve-oserror-constructor/34390 "2023-09-22T11:23:18Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [September 22, 2023, 11:23am UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/1 "2023-09-22T11:23:18Z")

</div>

The purpose of introducing multiple OSError subclasses (PEP 3151) was to allow user to write

```python
except FileNotFoundError:
    ...

```

instead of

```python
except OSError as e:
    if e.errno != errno.ENOENT:
        raise
    ...

```

OSError constructor now determines the actual type of exception by the _errno_ argument, so it works with both codes above. `OSError(errno.ENOENT, ...)` returns an instance of FileNotFoundError.

Of course, you can also call the subclass constructor explicitly. But if you call it with less than 2 arguments, the _errno_ and _strerror_ attributes are set to None. `FileNotFoundError(f'Directory does not exist: {dst}')` works with the former code, but not with the latter code.

I propose to infer the default value for _errno_ and _strerror_ from exception class if the constructor gets less than 2 positional arguments. You may want to pass additional arguments like _filename_ when skipping _errno_ and maybe _strerror_. For this, the constructor needs to support keyword arguments. The constructor signature will be a union of the following signatures:

```auto
(errno, strerror, /, filename=None, winerror=None, filename2=None)
(strerror, /, *, filename=None, winerror=None, filename2=None)
(*, filename=None, winerror=None, filename2=None)

```

So, you would be able to write `FileNotFound('Directory does not exist', filename=dst)` or even simply `FileNotFound(filename=dst)`.

> <https://github.com/python/cpython/issues/109714>
>
> \# Feature or enhancement
> 
> The purpose of introducing multiple OSError subclasses… was to allow user to write \`except FileNotFoundError:\` instead of \`except OSError as e: if e.errno != errno.ENOENT: raise\`. OSError constructor now determines the actual type of exception by the \*errno\* argument. Of course, you can also call the subclass constructor explicitly. Unfortunately, to get a good exception you need to explicitly specify \*errno\* argument: \`FileNotFoundError(errno.ENOENT, "No usable temporary directory found in %s" % dirlist)\`. "Good exception" is an exception which works with code that tests the \*errno\* attribute as well as with code that uses \`isinstance()\`. There is a lot of code in the stdlib and in the wild which calls OSError subclass constructors with a single argument or even without arguments. See #109601 for example.
> 
> I propose to infer the default value for \*errno\* (and maybe for strerror) from exception class. In case if several errno's are mapped to the same exception class, one of them is chosen as the default errno. When you omit \*errno\* argument, you may still want to provide \*filename\* or other optional arguments. For this, the constructor needs to support keyword arguments. The constructor signature will be a union of the following signatures:
> 
> \`\`\`
> (errno, strerror, /, filename=None, winerror=None, filename2=None)
> (strerror, /, \*, filename=None, winerror=None, filename2=None)
> (\*, filename=None, winerror=None, filename2=None)
> \`\`\`
> 
> So, you would be able to write \`FileNotFound('No such file', filename=path)\` or even simply \`FileNotFound(filename=path)\`.
> 
> cc @pitrou @warsaw

In the past I explicitly added _errno_ argument in my changes and suggested to do this in reviewd changes. Still, there is number of calls without _errno_. The following PR demonstrates the changes necessary for passing _errno_ and _strerror_ explicitly. It is enough combersome, so it would be more convenient to set this automatically.

> <https://github.com/python/cpython/pull/109720>
>
> It solves #109601, but covers not only TimeoutError, but other OSError subclasse…s.
> 
> With #109714 these changes will be unneeded (except that some calls may be improved to set \*filename\*), because \*errno\* and \*strerror\* will be set automatically. This PR is a demonstration of changes when do it explicitly. #109714 be accepted and we decide to apply these changes to older versions, this PR will be converted in a backport PR.

---

<div class="post-metadata">

**Author:** ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)\
**Post date:** [September 22, 2023, 11:33am UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/2 "2023-09-22T11:33:53Z")

</div>

(copying and expanding my reply from the GH issue)

Hmm, I wouldn’t be against it, but the reason I didn’t do it at the time is that `errno` is the system error indicator at the time was exception was raised. If you synthetize your own `errno` then it starts meaning something else: you can’t assume anymore that the system did set `errno` to that value at some point.

I have no idea how useful that assumption is, but it might be for some people.

Also, in practice, I’m not sure why someone would choose to manually write the `e.errno != errno.ENOENT` test instead of catching `FileNotFoundError`.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [September 22, 2023, 12:32pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/3 "2023-09-22T12:32:50Z")

</div>

> [@pitrou](#):
>
> Also, in practice, I’m not sure why someone would choose to manually write the `e.errno != errno.ENOENT` test instead of catching `FileNotFoundError`.

- It may be an old code, written before PEP 3151. It works well with FileNotFoundError raised implicitly by `open` but stops working when some other code changes EAFP to LBYL and raise FileNotFoundError explicitly. Also, we can introduce new OSError subclasses in future, the code that currently tests errno should still work.

- The error handler may be common for all OSError, but some parts only specific for specific errno. With separate `except` clauses you need to duplicate the common code.

- It may be test `e.errno in IGNORED_ERRNOS` where IGNORED\_ERRNOS is a set of errno values that correspond to different OSError subclasses or even do not have dedicated OSError subclass. You can see many examples in the stdlib and tests.

- On my past work, we translated some errors from server to client. OSError was translated as errno-message pair. If errno is None, it cannot be recreated at client side, so client could get general ClientError instead of more specific FileNotFoundError.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [September 22, 2023, 3:04pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/4 "2023-09-22T15:04:29Z")

</div>

> [@storchaka](#):
>
> It may be an old code, written before PEP 3151

I’m also not necessarily against it, but just to point out for sake of discussion, PEP 3151 was Python 3.3 so it probably is the least of your worries if you’re updating code that old to something more modern.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [September 22, 2023, 3:06pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/5 "2023-09-22T15:06:07Z")

</div>

I also just took a look and the WIP PR doesn’t make any documentation changes. The proposed signature change is complex enough that I’d really like to see how the new signature would be documented.

---

<div class="post-metadata">

**Author:** ![pitrou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pitrou/32/28_2.png) [@pitrou](https://discuss.python.org/u/pitrou)\
**Post date:** [September 22, 2023, 7:37pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/6 "2023-09-22T19:37:36Z")

</div>

> [@storchaka](#):
>
> - The error handler may be common for all OSError, but some parts only specific for specific errno. With separate `except` clauses you need to duplicate the common code.

You can just write `isinstance(e, FileNotFoundError)`, which is more natural than testing an obscure `errno` value. Symbolic names of errno values are so cryptic that few people remember them without having to look up the docs. It probably took me years to reliably remember “ENOENT”, which was probably the most frequently tested of all `errno` values.

> [@storchaka](#):
>
> - It may be test `e.errno in IGNORED_ERRNOS` where IGNORED\_ERRNOS is a set of errno values that correspond to different OSError subclasses or even do not have dedicated OSError subclass

I admit that’s a more reasonable use for `errno` checks.

> [@storchaka](#):
>
> On my past work, we translated some errors from server to client. OSError was translated as errno-message pair. If errno is None, it cannot be recreated at client side, so client could get general ClientError instead of more specific FileNotFoundError.

Hmm, either you use pickle, or if you can’t for security reasons, you can just send the exception class name to recreate it on the client.

---

<div class="post-metadata">

**Author:** ![EpicWink](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/epicwink/32/17968_2.png) [@EpicWink](https://discuss.python.org/u/EpicWink)\
**Post date:** [September 22, 2023, 11:37pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/7 "2023-09-22T23:37:38Z")

</div>

> [@pitrou](#):
>
> > [@storchaka](#):
> >
> > - It may be test `e.errno in IGNORED_ERRNOS` where IGNORED\_ERRNOS is a set of errno values that correspond to different OSError subclasses or even do not have dedicated OSError subclass
> 
> I admit that’s a more reasonable use for `errno` checks.

You could still replace that with `isinstance(e, IGNORED_OSERROR_TYPES)`

---

<div class="post-metadata">

**Author:** ![cameron](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cameron/32/2658_2.png) [@cameron](https://discuss.python.org/u/cameron)\
**Post date:** [September 23, 2023, 2:06am UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/8 "2023-09-23T02:06:07Z")

</div>

Is it guarrenteed that there’s a type for every `errno.THING` name? I  
did a quick grep over my person code, which has plenty of `e.errno == errno.ENOENT`  
style tests (yes, a lot well predate 3.3), and 2 examples particularly  
catch my eye:

```
 if e.errno == errno.ENAMETOOLONG and crop_ok:
 if e.errno not in (errno.ENOTSUP, errno.ENOENT, errno.ENODATA):

```

Do we have classes for all of these? I could look, but I tend to think  
of these names as a bit open ended - do we ensure class coverage for all  
the names `errno.THING` names when there’s a release for a platform?

And what happens on some oddball POSIXy system with an `errno` value not  
covered by the stdlib for some esoteric system call? I’d still need to  
do a numeric check against `e.errno` in that situation.

Cheers,  
Cameron Simpson [cs@cskk.id.au](mailto:cs@cskk.id.au)

---

<div class="post-metadata">

**Author:** ![gpshead](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gpshead/32/54_2.png) [@gpshead](https://discuss.python.org/u/gpshead)\
**Post date:** [September 23, 2023, 7:29pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/9 "2023-09-23T19:29:08Z")

</div>

> [@cameron](#):
>
> Is it guarrenteed that there’s a type for every `errno.THING` name?

Not all `errno` values have a specific `OSError` subclass, and many highly related `errno` values share the same subclass.

This was decided way back in the [PEP 3151 – Reworking the OS and IO exception hierarchy | peps.python.org](https://peps.python.org/pep-3151/#new-exception-classes) implementation days. The most commonly referenced ones were categorized and used to come up with the list of names. [PEP 3151 – Reworking the OS and IO exception hierarchy | peps.python.org](https://peps.python.org/pep-3151/#appendix-a-survey-of-common-errnos)

If you find an `errno` value lacking a named OSError subclass that you have need for, feel free to file a feature request. They can be added in the future.

---

<div class="post-metadata">

**Author:** ![gpshead](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gpshead/32/54_2.png) [@gpshead](https://discuss.python.org/u/gpshead)\
**Post date:** [September 23, 2023, 7:55pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/10 "2023-09-23T19:55:33Z")

</div>

Overall I’m in favor of this, we left ourselves with a design issue when creating the subclasses. It shouldn’t have been possible to create them without the proper errno matching what the class is for, set, at a minimum.

> [@barry](#):
>
> I also just took a look and the WIP PR doesn’t make any documentation changes. The proposed signature change is complex enough that I’d really like to see how the new signature would be documented.

I believe the WIP PR #109720 is an example of what is required to fix all our existing bugs _without_ having made the OSError & subclass constructor improvements. ie: it is backfilling places where we inadvertently did not pass the right arguments.

Agreed otherwise that it’d be good to see how we document the new signature, but a PR implementing that hasn’t been linked to from here or the issue yet.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [September 23, 2023, 8:35pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/11 "2023-09-23T20:35:04Z")

</div>

> [@barry](#):
>
> PEP 3151 was Python 3.3 so it probably is the least of your worries if you’re updating code that old to something more modern.

New OSError subclasses can be added in future.

> [@barry](#):
>
> The proposed signature change is complex enough that I’d really like to see how the new signature would be documented.

You can write it in less formal way as

```auto
([[errno,] strerror,] filename=None, winerror=None, filename2=None)

```

It is not new, the bracket syntax, even with nested brackets is used in other functions documentation. And optional first arguments are not new, see `range()` and a number of `curses` functions. But this syntax is less precise, for example it is not clear that first two parameters are positional-only and others are positional-or-keyword. I works on support of multi-signatures (postponed until 3.12 release due to more urgent matters), so I think that multiple signatures will be more familiar in near future.

> [@pitrou](#):
>
> You can just write `isinstance(e, FileNotFoundError)`, which is more natural than testing an obscure `errno` value.

Yes, of course. But there is less advantage of `isinstance(e, FileNotFoundError)` over `e.errno = errno.ENOENT` than of simple `except FileNotFoundError`. Be that as it may, such code exists.

> [@pitrou](#):
>
> Hmm, either you use pickle, or if you can’t for security reasons, you can just send the exception class name to recreate it on the client.

No, it was not pickle. It was simpler to do with errno than with exception class name. It worked well, because all implicitly raised OSError exceptions (except TimeoutError and UnsupportedOperation, but they did not play role) have errno, and for explicitly raised exceptions I provided it for constructor.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [August 26, 2026, 8:29pm UTC](https://discuss.python.org/t/improve-oserror-constructor/34390/12 "2026-08-26T20:29:11Z")

</div>

I finished the work started 3 years ago:

> <https://github.com/python/cpython/pull/156430>
>
> The \*errno\* and \*strerror\* arguments of the \`OSError\` constructor can now be omi…tted.
> 
> \*errno\* defaults to the error code which corresponds to the exception class, exposed as the new \`default\_errno\` class attribute, so that \`FileNotFoundError()\` works both with code that tests the \`errno\` attribute and with code that uses \`isinstance()\`.
> \*strerror\* is derived from the resulting \`errno\`, or on Windows from \*winerror\* if that was given, whose message is more specific.
> 
> \*filename\*, \*winerror\* and \*filename2\* can now be passed by keyword, as can \*characters\_written\* for \`BlockingIOError\`.
> 
> \`\`\`python
> \>\>\> str(FileNotFoundError(filename='cfg.ini'))
> "\[Errno 2\] No such file or directory: 'cfg.ini'"
> \`\`\`
> 
> \`args\` is now set to the canonical positional form, so that \`OSError(\*args)\` reproduces the exception, which is how it is pickled.
> It used to be truncated to two items whenever a file name was consumed, so an exception carrying one did not survive pickling.
> 
> Incompatible changes:
> 
> \* Passing more than five positional arguments is now a \`TypeError\`, instead of being silently ignored.
> \* \`args\` keeps the file names, so \`OSError(2, 'm', 'f').args\` is the whole 3-tuple rather than \`(2, 'm')\`.
> \* A file name given explicitly as \`None\` is not stored on the exception, so the args of a pickled \`OSError(2, 'm', None)\` lose the trailing \`None\`.
> 
> 
> 
> \* Issue: gh-109714
