[Python-ideas] Arguments to exceptions

Matthias Bussonnier bussonniermatthias at gmail.com
Wed Jul 5 09:29:35 EDT 2017


Hi all,

I want to point out that if it's not common to dispatch on values of
exceptions it might be **because** it is hard to do or to know wether
an exception will be structured or not. If Exceptions were by default
more structured, if CPython would provide a default
"StructuredException",  or were the norm because CPython would use
more of them –  then you might see more use case, and new code
patterns.

In particular the fact that you have to catch and exception, then
check values is annoying, I could see:

try:
   ...
except OSError(EACCES):
  # handle not permitted only
except OSError(ENOENT):
  # handle does not exists only
#implicit otherwise reraise

Be a much cleaner way of filtering exception _if_ python were to provide that.

Yes in Python 3 these are sometime their own subclass
(FileNotFoundError), but isn't the fact that you _have_ to subclass be
a hint that something might be wrong there ? You can see it in the
fact that FilenotFoundError need to be caught _before_ OSError, it is
not obvious to the beginner that FileNotFoundError is a subclass of
OSError and that you can't refactor by changing the excepts order.
Of course the custom subclass names are way more readable, (at least
to me) but it is easy as an experienced Python programmer to forget
what is difficult for newcommers. And this is (IMHO) one point which
is not easy.

One example I'm often wishing to have better filter is warnings where
you have to use a regular expression to mute (or turn into errors) a
subset of warnings.

Of course most of these use case can be dealt with by Subclassing and
having custom attributes (if you are in control of the raising code).
But nobody will use it if it's not encouraged by the core.

> Where there is a clear and obvious need the core devs have spent the
> time to give the exception class a rich API for extracting useful
> information. See OSError, which offers named attributes for the errno,
> error message, Windows error number (when appropriate) and two file
> names.

This does not encourage and show that doing structured exception is
hard and _not_ the standard.

> There should be one-- and preferably only one --obvious way to do it.

So structured or not ?

Please find post signature an extremely non scientific grep of all the
usage of structured information on Exceptions on my machine

Thanks,
-- 
Matthias

$ rg -tpy ' e\.[^g]' | grep -v test | grep -v ENOENT | grep if | grep
-v sympy | grep -v 'for e ' | grep -v getorg | grep -v ename |grep -v
errno
gh-activity/ghactivity.py:            source =
get_source_login(e.repo) if e.repo[0] == login else e.repo[0]
pypi/store.py:                if e.code == 204:
pypi-legacy/store.py:                if e.code == 204:
cpython/Lib/asyncore.py:        if e.args[0] not in _DISCONNECTED:
cpython/Lib/nntplib.py:            if e.response.startswith('480'):
cpython/Lib/pdb.py:                if e.startswith(text)]
cpython/Lib/runpy.py:            if e.name is None or (e.name != pkg_name and
flit/flit/upload.py:        if (not repo['is_warehouse']) and
e.response.status_code == 403:
gitsome/xonsh/execer.py:                if (e.loc is None) or
(last_error_line == e.loc.lineno and
git-cpython/Lib/asyncore.py:        if e.args[0] not in _DISCONNECTED:
git-cpython/Lib/nntplib.py:            if e.response.startswith('480'):
git-cpython/Lib/pdb.py:                if e.startswith(text)]
git-cpython/Lib/runpy.py:            if e.name is None or (e.name !=
pkg_name and
jupyter_client/jupyter_client/manager.py:                    if e.winerror != 5:
jupyterhub/jupyterhub/utils.py:            if e.code >= 500:
jupyterhub/jupyterhub/utils.py:                if e.code != 599:
procbuild/procbuild/builder.py:            if not 'Resource
temporarily unavailable' in e.strerror:
qtconsole/qtconsole/console_widget.py:        if e.mimeData().hasUrls():
qtconsole/qtconsole/console_widget.py:        elif e.mimeData().hasText():
qtconsole/qtconsole/console_widget.py:        if e.mimeData().hasUrls():
qtconsole/qtconsole/console_widget.py:        elif e.mimeData().hasText():
qtconsole/qtconsole/console_widget.py:        if e.mimeData().hasUrls():
qtconsole/qtconsole/console_widget.py:        elif e.mimeData().hasText():
xonsh/xonsh/execer.py:                if (e.loc is None) or
(last_error_line == e.loc.lineno and
xonsh/xonsh/execer.py:                if not greedy and maxcol in
(e.loc.column + 1, e.loc.column):
xonsh/xonsh/proc.py:            r = e.code if isinstance(e.code, int)
else int(bool(e.code))
django/django/forms/fields.py:                if hasattr(e, 'code')
and e.code in self.error_messages:
django/django/forms/fields.py:                errors.extend(m for m in
e.error_list if m not in errors)
django/django/http/multipartparser.py:            if not e.connection_reset:
cpython/Lib/asyncio/streams.py:            if
self._buffer.startswith(sep, e.consumed):
cpython/Lib/idlelib/MultiCall.py:                if not
APPLICATION_GONE in e.args[0]:
cpython/Lib/idlelib/MultiCall.py:                if not
APPLICATION_GONE in e.args[0]:
cpython/Lib/idlelib/MultiCall.py:                            if not
APPLICATION_GONE in e.args[0]:
cpython/Lib/multiprocessing/connection.py:                    if
e.winerror == _winapi.ERROR_BROKEN_PIPE:
cpython/Lib/multiprocessing/connection.py:                if
e.winerror != _winapi.ERROR_NO_DATA:
cpython/Lib/multiprocessing/connection.py:                if
e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
cpython/Lib/multiprocessing/process.py:            if not e.args:
git-cpython/cpython/Lib/asyncore.py:        if e.args[0] not in
(EBADF, ECONNRESET, ENOTCONN, ESHUTDOWN,
git-cpython/cpython/Lib/nntplib.py:                if user and
e.response[:3] == '480':
git-cpython/cpython/Lib/socket.py:                    if e.args[0] == EINTR:
git-cpython/cpython/Lib/socket.py:                    if e.args[0] == EINTR:
git-cpython/cpython/Lib/socket.py:                        if e.args[0] == EINTR:
git-cpython/cpython/Lib/socket.py:                    if e.args[0] == EINTR:
git-cpython/cpython/Lib/socket.py:                    if e.args[0] == EINTR:
git-cpython/Lib/asyncio/streams.py:            if
self._buffer.startswith(sep, e.consumed):
ipyparallel/ipyparallel/client/client.py:        if e.engine_info:
ipython/IPython/core/inputtransformer.py:        if 'multi-line
string' in e.args[0]:
ipython/IPython/core/inputsplitter.py:        if 'multi-line string'
in e.args[0]:
ipython/IPython/core/inputsplitter.py:        elif 'multi-line
statement' in e.args[0]:
git-cpython/Lib/idlelib/multicall.py:                if not
APPLICATION_GONE in e.args[0]:
git-cpython/Lib/idlelib/multicall.py:                if not
APPLICATION_GONE in e.args[0]:
git-cpython/Lib/idlelib/multicall.py:                            if
not APPLICATION_GONE in e.args[0]:
git-cpython/Lib/multiprocessing/connection.py:                    if
e.winerror == _winapi.ERROR_BROKEN_PIPE:
git-cpython/Lib/multiprocessing/connection.py:                if
e.winerror != _winapi.ERROR_NO_DATA:
git-cpython/Lib/multiprocessing/connection.py:                if
e.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
git-cpython/Lib/multiprocessing/process.py:            if not e.args:
jedi/jedi/api/completion.py:            if e.error_leaf.value == '.':
meeseeksbox/meeseeksdev/meeseeksbox/commands.py:        if ('git
commit --allow-empty' in e.stderr) or ('git commit --allow-empty' in
e.stdout):
meeseeksbox/meeseeksdev/meeseeksbox/commands.py:        elif "after
resolving the conflicts" in e.stderr:
notebook/notebook/notebook/handlers.py:            if e.status_code ==
404 and 'files' in path.split('/'):
pandas/pandas/core/strings.py:            if len(e.args) >= 1 and
re.search(p_err, e.args[0]):
pip/pip/_vendor/pyparsing.py:            if not e.mayReturnEmpty:
prompt_toolkit/prompt_toolkit/terminal/vt100_output.py:
elif e.args and e.args[0] == 0:
rust/src/etc/dec2flt_table.py:range of exponents e. The output is one
array of 64 bit significands and
rust/src/etc/htmldocck.py:        if e.tail:
rust/src/etc/htmldocck.py:        if attr in e.attrib:
setuptools/pkg_resources/_vendor/pyparsing.py:            if not
e.mayReturnEmpty:
scikit-learn/sklearn/datasets/mldata.py:            if e.code == 404:
numpy/tools/npy_tempita/__init__.py:            if e.args:
django/django/core/files/images.py:                if
e.args[0].startswith("Error -5"):
django/django/core/management/base.py:                        if e.is_serious()
cpython/Lib/xml/etree/ElementInclude.py:        if e.tag == XINCLUDE_INCLUDE:
cpython/Lib/xml/etree/ElementInclude.py:                if e.tail:
cpython/Lib/xml/etree/ElementInclude.py:        elif e.tag == XINCLUDE_FALLBACK:
cpython/Lib/xml/etree/ElementPath.py:                if e.tag == tag:
git-cpython/cpython/Demo/pdist/rcvs.py:            if not e.commitcheck():
git-cpython/cpython/Demo/pdist/rcvs.py:            if e.commit(message):
git-cpython/cpython/Demo/pdist/rcvs.py:            e.diff(opts)
git-cpython/cpython/Demo/pdist/rcvs.py:                if e.proxy is None:
git-cpython/cpython/Lib/multiprocessing/connection.py:            if
e.args[0] != win32.ERROR_PIPE_CONNECTED:
git-cpython/cpython/Lib/multiprocessing/connection.py:
if e.args[0] != win32.ERROR_PIPE_CONNECTED:
git-cpython/cpython/Lib/multiprocessing/connection.py:
if e.args[0] not in (win32.ERROR_SEM_TIMEOUT,
git-cpython/cpython/Lib/multiprocessing/process.py:            if not e.args:
git-cpython/Lib/xml/etree/ElementInclude.py:        if e.tag ==
XINCLUDE_INCLUDE:
git-cpython/Lib/xml/etree/ElementInclude.py:                if e.tail:
git-cpython/Lib/xml/etree/ElementInclude.py:        elif e.tag ==
XINCLUDE_FALLBACK:
git-cpython/Lib/xml/etree/ElementPath.py:                if e.tag == tag:
pip/pip/_vendor/distlib/locators.py:                    if e.code != 404:
pip/pip/_vendor/distlib/scripts.py:                if e.startswith('.py'):
pip/pip/_vendor/html5lib/serializer.py:                if not e.endswith(";"):
pythondotorg/blogs/management/commands/update_blogs.py:
    if e.pub_date < entry['pub_date']:
scikit-learn/doc/tutorial/machine_learning_map/svg2imagemap.py:    if
e.nodeName == 'g':
scikit-learn/doc/tutorial/machine_learning_map/svg2imagemap.py:    if
e.hasAttribute('transform'):
scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py:
 if not e.mayReturnEmpty:
scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py:
 if not e.mayReturnEmpty:
scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py:
 if e.mayReturnEmpty:
scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py:
     if e.mayReturnEmpty:
scikit-learn/doc/tutorial/machine_learning_map/pyparsing.py:
 if not e.mayReturnEmpty:
django/django/db/backends/mysql/base.py:            if e.args[0] in
self.codes_for_integrityerror:
django/django/db/backends/mysql/base.py:            if e.args[0] in
self.codes_for_integrityerror:
django/django/db/models/fields/__init__.py:                if
hasattr(e, 'code') and e.code in self.error_messages:
git-cpython/cpython/Lib/xml/etree/ElementInclude.py:        if e.tag
== XINCLUDE_INCLUDE:
git-cpython/cpython/Lib/xml/etree/ElementInclude.py:                if e.tail:
git-cpython/cpython/Lib/xml/etree/ElementInclude.py:        elif e.tag
== XINCLUDE_FALLBACK:
pip/pip/_vendor/requests/packages/urllib3/contrib/pyopenssl.py:
    if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
pip/pip/_vendor/requests/packages/urllib3/contrib/pyopenssl.py:
    if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
pip/pip/_vendor/requests/packages/urllib3/contrib/socks.py:
if e.socket_err:

On Wed, Jul 5, 2017 at 1:36 PM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Tue, Jul 04, 2017 at 11:37:51PM -0400, Terry Reedy wrote:
>
>> I personally been on the side of wanting richer exceptions.
>
> Could you explain what you would use them for? Ken has give two
> use-cases which I personally consider are relatively niche, and perhaps
> even counter-productive:
>
> - translation into the user's native language;
>
> - providing some sort of "did you mean...?" functionality.
>
> Jeff Walker also suggested being able to extract the line and column
> from certain kinds of JSON errors. (But that would depend on the json
> module having an API that supports that use-case. You can't just say
> line_no = exception.args[0] if there's no guarantee that it actually
> will be the line number.)
>
> What would you use these for? I imagine you're thinking of this as the
> maintainer of IDLE?
>
>
>> So what has
>> been the resistance?  Speed is definitely one.  Maybe space?  Probably
>> maintenance cost.  Lack of interest among true 'core' (C competent)
>> developers?
>
> Where there is a clear and obvious need the core devs have spent the
> time to give the exception class a rich API for extracting useful
> information. See OSError, which offers named attributes for the errno,
> error message, Windows error number (when appropriate) and two file
> names.
>
> I expect that the fact that few of the other builtin or stdlib
> exceptions similarly offer named attributes is because nobody thought
> of it, or saw any need.
>
>
>
> --
> Steve
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/


More information about the Python-ideas mailing list