Meaning of "with" statement without "as" keyword

KDN picture KDN · Oct 13, 2014 · Viewed 8.4k times · Source

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?

Answer

Martijn Pieters picture Martijn Pieters · Oct 13, 2014

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?