On Mon, 18 Nov 2019 at 17:46, Paul Moore firstname.lastname@example.org wrote:
On Mon, 18 Nov 2019 at 17:17, Oscar Benjamin email@example.com wrote:
The problem with a misbehaving context manager is that it creates a future need to call __exit__ before it has been passed to a with statement or any other construct that can guarantee to do that.
You seem to be focusing purely on the usage
with open(filename) as f: # use f
But open() isn't designed *just* to be used in a with statement. It can be used independently as well.
That's the problem I think. The context manager for closing the file is conflated as an object with the file object and its methods when they don't need to be the same object. This was considered in PEP 343: """ The problem is that in PEP 310, the result of calling EXPR is assigned directly to VAR, and then VAR's __exit__() method is called upon exit from BLOCK1. But here, VAR clearly needs to receive the opened file, and that would mean that __exit__() would have to be a method on the file. """ The discussion there shows that part of the design of the with statement was precisely so that it would not be necessary for the file object itself to *be* the context manager because __enter__ can return a different object.
f = open(filename) header = f.readline() with f: # use f
I would naturally rewrite that as
with open(filename) as f: header = f.readline() # use f
which would work just as well with opened instead of open. The opened function returns a context manager whose __enter__ method returns the file object which then has the corresponding file object methods.
You can wrap open() in a context manager like opened() that *does* work like that, but it's not the only way to write context managers. Certainly, nested() can't be written to safely work with the full generality of context managers as we currently have them, but as I said that's a trade-off.
I think that nested was fine but in combination with open it was prone to misuse. By the time with/contextlib etc had shipped in 2.5 it was easier to blame nested (which also had other flaws) so it took the fall for open.
Maybe I should ask the question the other way round. If we had opened(), but not open(), how would you write open() using opened()? It is, after all, easy enough to write opened() in terms of open().
The idea would be to have both so I don't think it matters but if you had opened and wanted to build open out of it then you could do:
def open(*args): return opened(*args).__enter__()
Anyway, I already said that where you choose to draw the line over what a context manager is (assuming you feel that the current definition is wrong), depends on your perspective.
It's not so much that the definition is wrong. It just isn't really defined and that makes it difficult to do anything fancy with multiple context managers. You need protocol contraints on both sides to be able to build useful utilities/patterns. The bar set for nested was that it should be able to recover from errors before it even gets called!
So I'm not trying to persuade you that I'm right over this. Unless this turns into a PEP to change the language (and I think it would need a PEP) it's just speculation and collecting opinions, so you have mine ;-)
A PEP seems premature as I'm not sure I have any clear solution...