If you want to be sure, I'd write the code like this:
from __future__ import with_statement
with open('foo') as f:
foo = f.read()
That way, your file closes as expected, even with exceptions.
Much later: here is some code with import dis
to show how the compiler treats these differently.
>>> def foo(filename):
... with open(filename) as f:
... return f.read()
...
>>> def bar(filename):
... return open(filename).read()
...
>>> from dis import dis
>>>
>>> dis(foo)
2 0 LOAD_GLOBAL 0 (open)
3 LOAD_FAST 0 (filename)
6 CALL_FUNCTION 1
9 DUP_TOP
10 LOAD_ATTR 1 (__exit__)
13 ROT_TWO
14 LOAD_ATTR 2 (__enter__)
17 CALL_FUNCTION 0
20 STORE_FAST 1 (_[1])
23 SETUP_FINALLY 23 (to 49)
26 LOAD_FAST 1 (_[1])
29 DELETE_FAST 1 (_[1])
32 STORE_FAST 2 (f)
3 35 LOAD_FAST 2 (f)
38 LOAD_ATTR 3 (read)
41 CALL_FUNCTION 0
44 RETURN_VALUE
45 POP_BLOCK
46 LOAD_CONST 0 (None)
>> 49 WITH_CLEANUP
50 END_FINALLY
51 LOAD_CONST 0 (None)
54 RETURN_VALUE
>>> dis(bar)
2 0 LOAD_GLOBAL 0 (open)
3 LOAD_FAST 0 (filename)
6 CALL_FUNCTION 1
9 LOAD_ATTR 1 (read)
12 CALL_FUNCTION 0
15 RETURN_VALUE