Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meaning of "with" statement without "as" keyword

I'm familiar with using python's with statement as a means of ensuring finalization of an object in the event of an exception being thrown. This usually looks like

with file.open('myfile.txt') as f:     do stuff... 

which is short-hand for

f = file.open('myfile.txt'): try:     do stuff... finally:     f.close() 

or whatever other finalization routine a class may present.

I recently came across a piece of code dealing with OpenGL that presented this:

with self.shader:     (Many OpenGL commands) 

Note that absence of any as keyword. Does this indicate that the __enter__ and __exit__ methods of the class are still to be called, but that the object is never explicitly used in the block (i.e., it works through globals or implicit references)? Or is there some other meaning that is eluding me?

like image 730
KDN Avatar asked Oct 13 '14 14:10

KDN


People also ask

What is the use of with statement?

Thus, with statement helps avoiding bugs and leaks by ensuring that a resource is properly released when the code using the resource is completely executed. The with statement is popularly used with file streams, as shown above and with Locks, sockets, subprocesses and telnets etc.

What is the with statement in Python?

In Python, the with statement replaces a try-catch block with a concise shorthand. More importantly, it ensures closing resources right after processing them. A common example of using the with statement is reading or writing to a file. A function or class that supports the with statement is known as a context manager.


2 Answers

The context manager can optionally return an object, to be assigned to the identifier named by as. And it is the object returned by the __enter__ method that is assigned by as, not necessarily the context manager itself.

Using as <identifier> helps when you create a new object, like the open() call does, but not all context managers are created just for the context. They can be reusable and have already been created, for example.

Take a database connection. You create the database connection just once, but many database adapters let you use the connection as a context manager; enter the context and a transaction is started, exit it and the transaction is either committed (on success), or rolled back (when there is an exception):

with db_connection:     # do something to the database 

No new objects need to be created here, the context is entered with db_connection.__enter__() and exited again with db_connection.__exit__(), but we already have a reference to the connection object.

Now, it could be that the connection object produces a cursor object when you enter. Now it makes sense to assign that cursor object in a local name:

with db_connection as cursor:     # use cursor to make changes to the database 

db_connection still wasn't called here, it already existed before, and we already have a reference to it. But whatever db_connection.__enter__() produced is now assigned to cursor and can be used from there on out.

This is what happens with file objects; open() returns a file object, and fileobject.__enter__() returns the file object itself, so you can use the open() call in a with statement and assign a reference to the newly created object in one step, rather than two. Without that little trick, you'd have to use:

f = open('myfile.txt') with f:     # use `f` in the block 

Applying all this to your shader example; you already have a reference to self.shader. It is quite probable that self.shader.__enter__() returns a reference to self.shader again, but since you already have a perfectly serviceable reference, why create a new local for that?

like image 118
Martijn Pieters Avatar answered Sep 23 '22 14:09

Martijn Pieters


The above answer is nicely put.

The only thing I kept asking myself while reading it, is where is the confirmation of the following scenario. In the event there is an assignment in the body of the context of the with statement, anything on the right side of the assignment is first "bound" to the context. So, in the following:

with db_connection():    result = select(...) 

... select is ~ ref_to_connection.select(...)

I put this here for anyone like me who comes and goes between languages and might benefit by a quick reminder of how to read and track the refs here.

like image 34
Edmund's Echo Avatar answered Sep 23 '22 14:09

Edmund's Echo