classification
Title: Replace contextmanager example and improve explanation
Type: enhancement Stage: resolved
Components: Documentation Versions: Python 3.7, Python 3.6, Python 3.4, Python 3.5, Python 2.7
process
Status: closed Resolution: duplicate
Dependencies: Superseder: Add try-finally contextlib.contextmanager example
View: 33468
Assigned To: docs@python Nosy List: berker.peksag, cowlinator, docs@python, martin.panter, ncoghlan, serhiy.storchaka, terry.reedy
Priority: normal Keywords: patch

Created on 2014-09-09 20:58 by terry.reedy, last changed 2018-07-26 05:37 by berker.peksag. This issue is now closed.

Files
File name Uploaded Description Edit
issue22374.diff berker.peksag, 2016-06-02 01:59 review
Messages (6)
msg226661 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-09-09 20:58
https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager
The current html contextmanager example is 'not recommended' for actual use, because there are better ways to accomplish the same goal.  To me, is also unsatifactory in that the context management is only metaphorical (conceptual) and not actual.

I propose the following as a replacement. It actually manages context and is, I believe, both useful and currently* the best way to accomplish the goal of temporarily monkeypatching a module,  It was directly inspired by #20752, see msg226657, though there have been other issues where monkeypatching as a solution has been discussed.

---
from contextlib import contextmanager
import itertools as its

@contextmanager
def mp(ob, attr, new):
    old = getattr(ob, attr)
    setattr(ob, attr, new)
    yield
    setattr(ob, attr, old)

def f(): pass

print(its.count, its.cycle)
with mp(its, 'count', f), mp(its, 'cycle', f):
    print(its.count, its.cycle)
print(its.count, its.cycle)

# <class 'itertools.count'>
# <function f at 0x00000000035A91E0>
# <class 'itertools.count'>
---

I am aware that the above does not follow the current style, which I dislike, of confusingly mixing together batch file code and interactive input code.  I think the above is how the example should be written.  It would work even better if Sphinx gave comment lines a different background color. (A '##' prefix could be used to differentiate code comments from commented-out output lines.)

In the same section, I find the following paragraph a bit confusing (perhaps is tries to say too much):

"contextmanager() uses ContextDecorator so the context managers it creates can be used as decorators as well as in with statements. When used as a decorator, a new generator instance is implicitly created on each function call (this allows the otherwise “one-shot” context managers created by contextmanager() to meet the requirement that context managers support multiple invocations in order to be used as decorators)."

I am guessing that this means, among other things, that ContextDecorator is necessary and sufficient to use mp twice in one with statement.  I intentionally added the double use to the example to make this possibility clear.

* The only better way I know of would be if mp (spelled out as 'monkeypatch' were considered useful enough to be added to contextlib.
msg226686 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2014-09-10 07:21
You should probably use try / finally in your context manager to always restore the attribute. Having said that, I recently wrote a similar context manager, and then later discovered there is already “unittest.mock.patch” and/or “unittest.mock.patch.object” via Issue 11664, which apparently can do this job.
msg266862 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2016-06-02 01:59
We also have swap_attr() and swap_item() helpers in Lib/test/support/__init__.py. I've used a simplified version of swap_attr() in my patch.

Since this is basically a document improvement I removed Python 2.7 from the versions field.
msg266918 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-06-02 19:04
swap_attr() looks too general. I think something more concrete would be better. But the documentation already contain other examples for contextmanager. closing() is good example, and redirect_stdout() would be good example.
msg311203 - (view) Author: cowlinator (cowlinator) Date: 2018-01-30 00:28
I would like to second the improved explanation of contextlib.contextmanager, and additionally point out another problem:  
A very important piece of information is missing from the documentation:  how to return data from the contextmanager-wrapped function.  

I had to go look up the source code, which had a wonderful explanation in the comments: https://gist.github.com/enuomi/1385336#file-contextlib-py-L56 

In particular, note that 

@contextmanager
def some_generator(<arguments>):
  yield <return_data>

can be used to return <return_data> to the caller, via

with some_generator(<arguments>) as <return_data>:
  print(return_data)

This information is wholly and completely missing from the contextlib.contextmanager documentation.
msg322397 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2018-07-26 05:37
The old tag() example has been replaced with a different example in https://github.com/python/cpython/commit/bde782bb594edffeabe978abeee2b7082ab9bc2a (bpo-33468)
History
Date User Action Args
2018-07-26 05:37:12berker.peksagsetstatus: open -> closed
superseder: Add try-finally contextlib.contextmanager example
messages: + msg322397

resolution: duplicate
stage: patch review -> resolved
2018-01-30 00:28:19cowlinatorsetnosy: + cowlinator

messages: + msg311203
versions: + Python 2.7, Python 3.4, Python 3.7
2016-06-02 19:04:47serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg266918
2016-06-02 01:59:50berker.peksagsetfiles: + issue22374.diff

type: enhancement
versions: + Python 3.6, - Python 2.7, Python 3.4
keywords: + patch
nosy: + berker.peksag

messages: + msg266862
stage: patch review
2014-09-10 07:21:53martin.pantersetnosy: + martin.panter
messages: + msg226686
2014-09-09 20:58:12terry.reedycreate