This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Suppress (and other contextlib context managers) should work as decorators (where appropriate)
Type: Stage: resolved
Components: Library (Lib) Versions: Python 3.7
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: barry, jaraco, r.david.murray, rhettinger
Priority: normal Keywords:

Created on 2017-11-28 19:39 by jaraco, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (5)
msg307155 - (view) Author: Jason R. Coombs (jaraco) * (Python committer) Date: 2017-11-28 19:39
The contextlib docs explain why a decorator is nice:

> It makes it clear that the cm applies to the whole function, rather than just a piece of it (and saving an indentation level is nice, too).

However, the built-in context managers that could readily supply this interface don't derive from DecoratorContext.

In jaraco.context, I [added decorator support by simply deriving from the two base classes](https://github.com/jaraco/jaraco.context/commit/5aa7b0bb222cff5009a2f0dc3ea49db9e7a6b71a#diff-efbedfbbcb7f61268cfeff04a32fa59d).

But it got me thinking - couldn't suppress (and possibly other) contextlib decorators support this usage out of the box?

If there's interest, I'll put together a patch with test.
msg307157 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2017-11-28 19:50
> But it got me thinking - couldn't suppress (and possibly other)
> contextlib decorators support this usage out of the box?

They possibly could but probably shouldn't.   My experience is that giving them a dual role makes them more complicated and harder to understand.

For suppress() in particular, wrapping a whole function is likely an anti-pattern.  Usually we advise people to put as little as possible in the try-block to avoid catching unexpected exceptions.

Also, I think it would be inevitable that people would try to apply these to generators or awaitables and find that they don't mix well.
msg307158 - (view) Author: Jason R. Coombs (jaraco) * (Python committer) Date: 2017-11-28 19:58
Fair enough.

For an example, here's the case where I wanted to use the decorator to avoid excess indentation and keep the most meaningful part of the function at the base of the body:

@suppress(KeyError)
def v12_to_13(manager, case):
    case['sample_id'] = case.pop('caseid')


In my opinion, it's nominally nicer and clearer than:

def v12_to_13(manager, case):
    with suppress(KeyError):
        case['sample_id'] = case.pop('caseid')


But I see your points about encouraging overly-broad catching of exceptions... so it's better to have the indentation as something of a wart to dissuade excess wrapping.
msg311899 - (view) Author: Jason R. Coombs (jaraco) * (Python committer) Date: 2018-02-09 17:39
In [this question](https://stackoverflow.com/a/48710609/70170), I encounter another case where a decorator would be useful. Without the decorator:

def is_docker():
	path = '/proc/self/cgroup'
	return (
		os.path.exists('/.dockerenv')
		or os.path.isfile(path)
		and any('docker' in line for line in open(path))
	)

With the decorator:

@suppress(FileNotFoundError)
def is_docker():
	return (
		os.path.exists('/.dockerenv')
		or any('docker' in line for line in open('/proc/self/cgroup'))
	)

The decorator enables several improvements:

- The boolean expression is now two expressions joined by 'or', which is semantically easier to parse and thus less prone to error than the three joined by and/or.
- There's no longer a need to create a path variable and reference it twice, allowing the value to appear inline where it's most relevant.
- The code is one line shorter.
- The body of the function is two lines shorter.
- The key behaviors the function is seeking to achieve are prominently presented.

Acknowledged there are two caveats:

- It's unclear the exception really is only expected in the 'open' call.
- In the case where the exception is suppressed, the function will return None, which while resolving to boolean False, isn't False.

Those caveats could be addressed, but will sacrifice readability or conciseness.

I don't think this use-case warrants re-opening the ticket or revisiting the issue, but I wanted to share for consideration.
msg311903 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2018-02-09 19:33
The designed use case of suppress is to cover a *single line* of python code.  It should *not* be a decorator.
History
Date User Action Args
2022-04-11 14:58:54adminsetgithub: 76339
2018-02-09 19:33:23r.david.murraysetnosy: + r.david.murray
messages: + msg311903
2018-02-09 17:39:42jaracosetmessages: + msg311899
2017-11-28 20:02:25barrysetnosy: + barry
2017-11-28 19:58:59jaracosetstatus: open -> closed
resolution: rejected
messages: + msg307158

stage: resolved
2017-11-28 19:50:20rhettingersetnosy: + rhettinger
messages: + msg307157
2017-11-28 19:39:46jaracocreate