E pur si muove

Return inside with statement (updated)

Saturday, August 14, 2010

Somehow my brain seems to think there's a reason not to return inside a with statement, so rather then doing this:

def foo():
    with ctx_manager:
        return bar()

I always do:

def foo():
    with ctx_manager:
         result = bar()
    return result

No idea why nor where I think to have heard/read this. Searching for this brings up absolutely no rationale. So if you know why this is so, or know that the first version is perfectly fine, please enlighten me!


Seems it's only relevant if you're reading a file using the with statement. This seems to have come from the python documentation itself:

The last version is not very good either — due to implementation details, the file would not be closed when an exception is raised until the handler finishes, and perhaps not at all in non-C implementations (e.g., Jython).

def get_status(file):
    with open(file) as fp:
        return fp.readline()

Sadly it doesn't say what the implementation details are nor how to do this correctly. I can have several more or less educated guesses at why and which way to do this better. But I'd love to get a more detailed description of what happens in the implementations when doing this, because the worst-case way of interpreting that example is that using open() or file() as a context manager is completely useless. Which I would hate.

Saturday, August 14, 2010 | Labels: |


Mats Gefvert said...

Rings a bell with me, being an old Delphi hacker... There's no return statement in Delphi, the return value is always contained in a variable called Result. So, you assign a value to Result and then exit the function. Maybe that's where it comes from?

bc said...

I think that the with-statement was conceived to solve some of the issues with try-finally. For example, returning from within a finally-block will "lose" an unhandled exception due to the try-block which would otherwise have been raised after the finally-clause. See this ipython session:

In [1]: class MyError(Exception):
...: pass

In [2]: def func():
...: try:
...: raise MyError
...: finally:
...: print "all OK"
...: return 0

In [3]: func()
all OK
Out[3]: 0

Losing the exception is a Bad Thing, hence your idiom of placing the return outside the finally-clause has arisen. The with-statement is intended to be used where otherwise a try-finally clause would have been neceessary, but avoids some of these try-finally pitfalls. Returning from within a with-block is fine. said...

You just blew my mind. How come that the result is visible out of the with scope? Does it work with the try block too? I have to try it...omg it works too.

Ozk said...

There should be no problem returning a value from inside a with statement.
here is a quick example you can try out:

Oz K

Unknown said...

I know there's no problem with returning from within it, that's exactly the point of a context manager! It runs the __exit__() no matter what (and __exit__() can decide to swallow an exception or not). I was more thinking of some strange CPython performance side-effect or maybe a frame that hangs around longer then you would normally expect. Something strange and obscure anyway, I have no questions about the basic working of a context manager.

Probably just made it up.

peter9477 said...

In response to, the reason this blew your mind is because you think Python can even have multiple scopes inside a function. It doesn't, so there's only one "scope" here, and no reason (once you know that) to be surprised by it. It is exactly like doing a return inside a try, with a finally after...

Masklinn said...

> No idea why nor where I think to have heard/read this.

There was a trend at one point of never having more than a single return statement in a function (and as Mats points out, some language mandated it). Probably stems from there.

There is absolutely no reason to do that, returns work just fine from within `with` statements.

> You just blew my mind. How come that the result is visible out of the with scope?

Because Python only has function-level variable scopes, similar to Javascript (1.5 anyway). A variable created in an if, while, for, with, etc... block is bound in the function scope and thus visible outside the block.

Anonymous said...

This is interesting/sobering. I'd always assumed that __exit__() was run before the exception-handling code. It's also very strange that if it's true that it might never be run in Jython, since I thought the whole point of the context manager is to guarantee that it would be run. I wonder if that comment in the docs is wrong??

Unknown said...

Reading the checkin message for that with statement it sounds like this is also the case for a normal "fp=open(file); try:; finally: fp.close()". So not a deficiency of the with statement, just something involving files and finally. Still quirky though.

Anonymous said...

I think you are misunderstanding that quote. The "last version" referred to in the quote is the one without the "with" statement. *That* version has the problem described where the file may not be closed in e.g. Jython. The whole point of the version using the with-statement is to fix this.

Unknown said...

Aha! Now that would make a lot more sense, can't believe I've just been reading this the wrong way. I'll see if I can come up with a patch to make it more obvious.

Anonymous said...

I have to say I read the Python documentation the same way as you at first, but it is very confusing. If it is to be understood as "Anonymous" interpreted it, then it explicitly says the "best version" is "probably" the second-to-last, the one without the "with" statement, and then exposes a flaw in it, and just tags on the end a version that is better than the "best" version with no comment.

New comments are not allowed.

Subscribe to: Post Comments (Atom)