classification
Title: Rename contextlib.ignore to contextlib.suppress
Type: enhancement Stage: committed/rejected
Components: Library (Lib) Versions: Python 3.4
process
Status: closed Resolution: fixed
Dependencies: 15806 Superseder:
Assigned To: ncoghlan Nosy List: barry, belopolsky, ncoghlan, python-dev, r.david.murray, rhettinger, skrah, zero.piraeus
Priority: normal Keywords: patch

Created on 2013-10-15 11:50 by ncoghlan, last changed 2013-10-17 13:42 by python-dev. This issue is now closed.

Files
File name Uploaded Description Edit
cligsu.patch zero.piraeus, 2013-10-15 13:33 review
Messages (22)
msg199995 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-15 11:50
Issue 15806 added contextlib.ignored to the standard library (later renamed to contextlib.ignore), as a simple helper that allows code like:

    try:
        os.remove(fname)
    except FileNotFoundError:
        pass

to instead be written as:

    with ignore(FileNotFoundError):
        os.remove('somefile.tmp')

The point has been made that "ignore" may easily be taken to mean preventing the exception being raised *at all* (since truly ignoring the exception would mean not skipping the rest of the with statement), rather than suppressing the exception in the context manager's __exit__ method.

If you look at the rest of the contextlib docs, as well as the docs for the with statement and context manager objects, they don't refer to returning True from __exit__ as ignoring the exception, but rather as *suppressing* it. Even the docs for contextlib.ignore now make use of the term "suppress".

So I think it makes sense to rename the context manager to "suppress", while keeping the same semantics:

    with suppress(FileNotFoundError):
        os.remove('somefile.tmp')
msg199996 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-15 13:01
The specific docs quotes that persuaded me "suppress" was a better name than "ignore" for this feature (by contrast, "ignore" in this sense only appears in its own docs):

From http://docs.python.org/dev/library/stdtypes.html#contextmanager.__exit__:

"Exit the runtime context and return a Boolean flag indicating if any
exception that occurred should be suppressed."

"Returning a true value from this method will cause the with statement
to suppress the exception and continue execution with the statement
immediately following the with statement. "

From http://docs.python.org/dev/reference/datamodel.html#object.__exit__

"If an exception is supplied, and the method wishes to suppress the
exception (i.e., prevent it from being propagated), it should return a
true value."

From http://docs.python.org/dev/library/contextlib#contextlib.contextmanager

"If an exception is trapped merely in order to log it or to perform
some action (rather than to suppress it entirely), the generator must
reraise that exception."

From http://docs.python.org/dev/library/contextlib#contextlib.ignore (!)

"As with any other mechanism that completely suppresses exceptions, it
should only be used to cover very specific errors where silently
ignoring the exception is known to be the right thing to do."

From http://docs.python.org/dev/library/contextlib#contextlib.ExitStack

"...if an inner callback suppresses or replaces an exception, then
outer callbacks will be passed arguments based on that updated state."

From http://docs.python.org/dev/library/contextlib#contextlib.ExitStack.enter_context

"These context managers may suppress exceptions just as they normally
would if used directly as part of a with statement."

From http://docs.python.org/dev/library/contextlib#contextlib.ExitStack.push

"By returning true values, these callbacks can suppress exceptions the
same way context manager __exit__() methods can."

From http://docs.python.org/dev/library/contextlib#contextlib.ExitStack.callback

"Unlike the other methods, callbacks added this way cannot suppress
exceptions (as they are never passed the exception details)."
msg199997 - (view) Author: Zero Piraeus (zero.piraeus) * Date: 2013-10-15 13:33
This is my first submitted patch; if there's anything wrong with it, please let me know (but the testsuite passes, and make patchcheck only warns about Misc/NEWS and Misc/ACKS, which I assume is handled by committer).
msg199998 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-15 13:55
Zero's patch looks good to me, but it may be a couple of days before I can get to applying it. If anyone else can handle it before then, please feel free :)

Also, Zero, if you could review and sign the contributor agreement, that would be great: http://www.python.org/psf/contrib/contrib-form/ (while this patch is mechanical enough to be OK without one, it's still good to take care of the formalities of granting the PSF clear redistribution rights for CPython contributions)
msg199999 - (view) Author: Zero Piraeus (zero.piraeus) * Date: 2013-10-15 14:20
> Zero, if you could review and sign the contributor agreement, that
> would be great: http://www.python.org/psf/contrib/contrib-form/

Done :-)
msg200007 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2013-10-15 16:28
This patch looks fine.  I'll apply it shortly.
msg200037 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2013-10-16 07:25
After more thought, I think that suppress() isn't as clear as ignore() and it doesn't read as well in typical use cases.  I'm assigning this one back to Nick to decide.

If you want to scan existing code for examples to see how well this would read, run this:

    $ egrep -C2 "except( [A-Za-z]+)?:" *py  | grep -C2 "pass"
msg200039 - (view) Author: STINNER Victor (haypo) * (Python committer) Date: 2013-10-16 07:47
On python-dev, abort_on() and trap() were proposed.
msg200044 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2013-10-16 09:11
I oppose abort_on() because it implies that it aborts the program.

The word trap() is accurate but will be weird-sounding and non-communicative to users without a CS background:

    with trap(sqlite3.OperationalError):
        cursor.execute('CREATE TABLE dict (key text, value text)')

The word "trap" in a CS context is also archaic and falling out of use.  (Remember, glob.glob() was a good name in 1990 but most people now don't get know the reference to "globbing" and so the word is meaningless gobbledygook to them).

Please give some weight to the fact the ignore() was checked in for seven months, it was presented at a conference, I've put it front of working Python programmers to use in real code, and I've checked to see how it reads in the try/except/pass code examples in the standard library.   Don't throw away this work on a whim.
msg200052 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2013-10-16 10:18
trap() is a bit ambiguous, since in floating point operations it
means that something is actually raised and not suppressed. So one
could write:

from decimal import *
c = getcontext()
c.traps[Inexact] = True
>>> Decimal(9) / 11 # raises now!

with trap(Inexact):
    Decimal(9) / 11 # quiet!



As for "ignore" vs. "suppress", I'm with the people who think that they
are largely synonyms here.  I find "ignore" slightly catchier and nicer
to read.  Being pedantic, one could call it "ignore_once".

I would also like "catch", or pedantically, "catch_once".
msg200054 - (view) Author: Zero Piraeus (zero.piraeus) * Date: 2013-10-16 12:16
'Ignore' and 'suppress' are not synonyms:

https://www.google.com/search?q=define%3Asuppress

> forcibly put an end to.
> "the rising was savagely suppressed"
> synonyms: subdue, repress, crush, quell, quash, squash, stamp out

https://www.google.com/search?q=define%3Asuppress

> refuse to take notice of or acknowledge; disregard intentionally.
> "he ignored her outraged question"
> synonyms: disregard, take no notice of, pay no attention to [...]

I know that ncoghlan and rhettinger (and maybe others) are annoyed by what they see as bikeshedding, but there is a genuine issue here. To summarize the objection raised on python-dev, the problem is that this:

    with ignore(SomeException):
        do_something()
        do_something_else()

... is easily misunderstood as ignoring every occurrence of SomeException throughout the with-statement. 

If you understand how context managers work, it's not difficult to see why that's not the case, but the name strongly suggests the incorrect reading over the correct one.

I don't think 'suppress' is perfect. At the risk of further enraging those who are already tired of this discusion, I'll re-propose 'silence', which IMO comes closest to describing what is actually going on:

https://www.google.com/search?q=define%3Asilence

> cause to become silent; prohibit or prevent from speaking.
> "the team's performance silenced their critics"
> synonyms: quiet, hush, shush

I also quite like 'quash' from the list of 'suppress' synonyms above, but that's probably just because it's a nice word to say :-)
msg200056 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2013-10-16 12:28
Zero Piraeus <report@bugs.python.org> wrote:
> 'Ignore' and 'suppress' are not synonyms:

I wrote "synonyms here", meaning that in *this context* they are practically
synonyms. "suppress" describes the mechanics more precisely, "ignore"
descibes the human intent: suppress_and_thereby_ignore.
msg200091 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2013-10-16 22:40
> Please give some weight to the fact the ignore() was
> checked in for seven months, ...

+1
msg200092 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2013-10-16 22:55
Yes, in this context ingnore, suppress, and silence all have essentially the same problem, or lack of it, depending on your point of view.

Catch would be fine with me :)

Please note that someone *reading the thread* on python-dev misunderstood what ignore did after *reading the documentation*.  So, whether or not the name is changed, the documentation should be updated to stress the fact that the with block is exited as soon as the exception is raised for the first time.
msg200093 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2013-10-16 23:02
To be clear: I do think 'suppress' is better than 'ignore', for the reasons Nick articulated.
msg200094 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2013-10-16 23:04
On Oct 16, 2013, at 3:55 PM, R. David Murray <report@bugs.python.org> wrote:

> Catch would be fine with me :)

I like "catch".

Raymond
msg200095 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-16 23:18
I didn't choose suppress on a whim. I actually agree with Raymond that
ignore reads better when the context manager is used correctly, but
suppress is more consistent with the terminology used in the documentation
(including even PEP 343), *and* I think it is slightly less vulnerable to
people expecting it to mean "don't even raise the exception and continue
with the next statement inside the with block" (aka the "on error resume
next" misinterpretation).

I think suppress reads *well enough* for it to be worth making the switch
in order to gain those other benefits.
msg200096 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-16 23:21
The reason I specifically *don't* like trap or catch for this is that they
both have "... and do something with it" connotations for me, whereas
ignore and suppress both appropriately imply "... and silently discard it".
msg200097 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2013-10-16 23:24
> Catch would be fine with me :)

Both "catch" and "trap" have the same problem in my view: you don't get to eat what you have caught (or trapped).  :-)


> Please note that someone *reading the thread* on python-dev
> misunderstood what ignore did after *reading the documentation*.

I question whether the confusion was genuine.  Anyone who has discovered contextlib modules should know enough about with statement, context managers and exceptions to understand how ignore() can work.  Sky is the limit when it comes to documentation improvements, but in this case code is better than a thousand words:

  @contextmanager
  def ignore(*exceptions):
    """Context manager to ignore particular exceptions"""
    try:
        yield
    except exceptions:
        pass


Here is how I understand the word "ignore" in the context of context managers. (Pun unavoidable.)  The context manager implements logic of how to exit the with block.  The logic of ignore() CM is to (drum roll, please) ignore the specified exception(s) if any is raised within the with block.

I gave my +0 to "suppress" on the list, but with more thought and considering more examples, I like "ignore" best.  It is still a close call, but "suppress" suggests more effort on the part of CM than there is.
msg200098 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-10-16 23:29
Agreed it's a close call - it's really the docs consistency issue that
tipped the balance for me, since I think either ignore *or* suppress would
be a suitable name for the pattern when used correctly.
msg200100 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2013-10-16 23:38
Feel free to ignore() me if it helps to close this debate.  English is not my native language and my understanding may not match that of the majority of users.

Note, however, that this debate might not even have started if not for a change s/ignored/ignore/.  The s/ignore/suppress/ commit may sparkle yet another round.

(It is quite possible that my dislike of "suppress" stems from not being able to remember how many p's and s's this word has in its spelling.  As I said - feel free to ignore me.)
msg200126 - (view) Author: Roundup Robot (python-dev) Date: 2013-10-17 13:42
New changeset 22247b7d17fa by Nick Coghlan in branch 'default':
Close #19266: contextlib.ignore -> contextlib.suppress
http://hg.python.org/cpython/rev/22247b7d17fa
History
Date User Action Args
2013-10-17 13:42:52python-devsetstatus: open -> closed

nosy: + python-dev
messages: + msg200126

resolution: fixed
stage: needs patch -> committed/rejected
2013-10-16 23:47:02hayposetnosy: - haypo
2013-10-16 23:38:18belopolskysetmessages: + msg200100
2013-10-16 23:29:47ncoghlansetmessages: + msg200098
2013-10-16 23:24:02belopolskysetmessages: + msg200097
2013-10-16 23:21:37ncoghlansetmessages: + msg200096
2013-10-16 23:18:19ncoghlansetmessages: + msg200095
2013-10-16 23:04:47rhettingersetmessages: + msg200094
2013-10-16 23:02:59r.david.murraysetmessages: + msg200093
2013-10-16 22:55:11r.david.murraysetnosy: + r.david.murray
messages: + msg200092
2013-10-16 22:40:48belopolskysetnosy: + belopolsky
messages: + msg200091
2013-10-16 22:31:55belopolskylinkissue15806 superseder
2013-10-16 22:31:31belopolskysetdependencies: + Add context manager for the "try: ... except: pass" pattern
2013-10-16 12:28:30skrahsetmessages: + msg200056
2013-10-16 12:16:59zero.piraeussetmessages: + msg200054
2013-10-16 10:18:46skrahsetnosy: + skrah
messages: + msg200052
2013-10-16 09:11:29rhettingersetmessages: + msg200044
2013-10-16 07:47:50hayposetnosy: + haypo
messages: + msg200039
2013-10-16 07:25:20rhettingersetassignee: rhettinger -> ncoghlan
messages: + msg200037
2013-10-15 16:28:43rhettingersetassignee: rhettinger
messages: + msg200007
2013-10-15 14:20:31zero.piraeussetmessages: + msg199999
2013-10-15 13:55:00ncoghlansetmessages: + msg199998
2013-10-15 13:33:30zero.piraeussetfiles: + cligsu.patch

nosy: + zero.piraeus
messages: + msg199997

keywords: + patch
2013-10-15 13:15:30barrysetnosy: + barry
2013-10-15 13:01:29ncoghlansetmessages: + msg199996
2013-10-15 11:50:54ncoghlancreate