Context Managers have existed in Python since version 2.5, while Assignment Expressions (walrus operator) were added in version 3.8. Somehow recently I came up to wondering if we can replace the "as" by a ":=" assignment. I mean, can we do this?:
with it := MyContextManager():
# do whatever with it
rather than this:
with MyContextManager() as it:
# do whatever with it
The answer is NO, or well, more accurately, sometimes yes, sometimes no, but you should always avoid it. To understand this we have to review what a Context Manager is and how they work. Notice that they're part of a broader concept: Automatic Resource Management that also includes Garbage Collecion and RAII (Resource Acquisition Is Initialization).
A Python Context Manager handles the setup and cleanup of resources in your programs. A context manager is any object that implements:
__enter__(self)
__exit__(self, exc_type, exc_value, traceback)
And it's used like this:
with EXPR as target:
BODY
And now the important part. Conceptually, Python does something roughly like this (as explained by a GPT):
resource_manager = EXPR
resource = resource_manager.__enter__()
try:
target = resource
BODY
finally:
manager.__exit__(...)
So the key point is: The object used to manage the context and the object bound after as do not have to be the same object. That is exactly why __enter__() is allowed to return anything.
Some Context Managers are implemented so that __enter__ returns the context manager itself, while others return a different object. Basically, in the first case the resource being managed and the Resource Manager (Context Manager) are the same object, in the second case they are different as the management responsability has been moved away from the resource itself, to a different object.
Another interesting topic. We know that in Python a single with block can include multiple context managers. I mean:
with open('a.txt', 'r') as fr, open('b.txt', 'w') as fw:
do_something(fr, fw)
I was wondering if cases where the second context manager makes use of the first context manager, and that I think is more common to find written like this:
with ContextManager1("aaa") as ctx1:
with ContextManager2(ctx1) as ctx2:
do_something(ctx1, ctx2)
Could be written with a single with (avoiding the additional nesting level):
with ContextManager1("aaa") as ctx1, ContextManager2(ctx1) as ctx2:
do_something(ctx1, ctx2)
The answer is YES. The first
Python evaluates multiple context managers in a single with statement sequentially from left to right. The moment the first context manager is entered, its return value is bound to the as variable, making it immediately available for the next context manager on the same line.
No comments:
Post a Comment