Reputation: 99418
If I am correct, with
statement doesn't introduce a local scope for the with
statement.
These are examples from Learning Python:
with open(r'C:\misc\data') as myfile:
for line in myfile:
print(line)
...more code here...
and
lock = threading.Lock() # After: import threading
with lock:
# critical section of code
...access shared resources...
Is the second example equivalent to the following rewritten in a way similar to the first example?
with threading.Lock() as lock:
# critical section of code
...access shared resources...
What are their differences?
Is the first example equivalent to the following rewritten in a way similar to the second example?
myfile = open(r'C:\misc\data')
with myfile:
for line in myfile:
print(line)
...more code here...
What are their differences?
Upvotes: 3
Views: 70
Reputation: 1121734
When with
enters a context, it calls a hook on the context manager object, called __enter__
, and the return value of that hook can optionally be assigned to a name using as <name>
. Many context managers return self
from their __enter__
hook. If they do, then you can indeed take your pick between creating the context manager on a separate line or capturing the object with as
.
Out of your two examples, only the file object returned from open()
has an __enter__
hook that returns self
. For threading.Lock()
, __enter__
returns the same value as Lock.acquire()
, so a boolean, not the lock object itself.
You'll need to look for explicit documentation that confirms this; this is not always that clear however. For Lock
objects, the relevant section of the documentation states:
All of the objects provided by this module that have
acquire()
andrelease()
methods can be used as context managers for awith
statement. Theacquire()
method will be called when the block is entered, and release() will be called when the block is exited.
and for file objects, the IOBase
documentation is rather on the vague side and you have to infer from the example that the file object is returned.
The main thing to take away is that returning self
is not mandatory, nor is it always desired. Context managers are entirely free to return something else. For example, many database connection objects are context managers that let you manage the transaction (roll back or commit automatically, depending on whether or not there was an exception), where entering returns a new cursor object bound to the connection.
To be explicit:
for your open()
example, the two examples are for all intents and purposes exactly the same. Both call open()
, and if that does not raise an exception, you end up with a reference to that file object named myfile
. In both cases the file object will be closed after the with
statement is done. The name continues to exist after the with
statement is done.
There is a difference, but it is mostly technical. For with open(...) as myfile:
, the file object is created, has it's __enter__
method called and then myfile
is bound. For the myfile = open(...)
case, myfile
is bound first, __enter__
called later.
For your with threading.Lock() as lock:
example, using as lock
will set lock
to a True
(locking always either succeeds or blocks indefinitely this way). This differs from the lock = threading.Lock()
case, where lock
is bound to the lock object.
Upvotes: 4
Reputation: 2154
Here's a good explanation. I'll paraphrase the key part:
The
with
statement could be thought of like this code:set things up try: do something finally: tear things down
Here, “set things up” could be opening a file, or acquiring some sort of external resource, and “tear things down” would then be closing the file, or releasing or removing the resource. The try-finally construct guarantees that the “tear things down” part is always executed, even if the code that does the work doesn’t finish.
Upvotes: 1