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?
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.
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.
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With