Python's 'with' statement versus 'with .. as'
Asked Answered
M

4

20

Having just pulled my hair off because of a difference, I'd like to know what the difference really is in Python 2.5.

I had two blocks of code (dbao.getConnection() returns a MySQLdb connection).

conn = dbao.getConnection()
with conn:
    # Do stuff

And

with dbao.getConnection() as conn:
    # Do stuff

I thought these would have the same effect but apparently not as the conn object of the latter version was a Cursor. Where did the cursor come from and is there a way to combine the variable initialization and with statement somehow?

Meri answered 24/5, 2009 at 11:17 Comment(2)
The second version does initialize a variable, conn. What actual problem are you having? What worked differently? What error did you get? Can you include some output to show the problem?Alcinia
Sorry. Thought it would have been clear from the description. dbao.getConnection() returns a MySQLdb connection so conn = dbao.getConnection() results in a conn being a Connection object while "with dbao.getConnection() as conn" results in conn being a Cursor object. The error message was that in the latter case conn did not have a rollback method which it shouldn't have as it was a Cursor.Meri
N
21

In general terms, the value assigned by the as part of a with statement is going to be whatever gets returned by the __enter__ method of the context manager.

Necrose answered 24/5, 2009 at 18:18 Comment(1)
Was suspecting this. Thanks for the confirmation!Meri
C
39

It may be a little confusing at first glance, but

with babby() as b:
    ...

is not equivalent to

b = babby()
with b:
    ...

To see why, here's how the context manager would be implemented:

class babby(object):
    def __enter__(self):
        return 'frigth'

    def __exit__(self, type, value, tb):
        pass

In the first case, the name b will be bound to whatever is returned from the __enter__ method of the context manager. This is often the context manager itself (for example for file objects), but it doesn't have to be; in this case it's the string 'frigth', and in your case it's the database cursor.

In the second case, b is the context manager object itself.

Cheboksary answered 24/5, 2009 at 20:34 Comment(4)
At times like these I wish I could award two answers. :|Meri
@Mikko: IMHO this is the better -- if for not other reason than it is more detailed and discusses the differences -- answer and should be the accepted one.Definitely
@martineau: Since we're commenting on old comments! Yes, this answer is more detailed and discusses the differences. It gives more background information and in that sense it is better. But as an answer it is upside down. The accepted answer manages to answer the specific question in the (first) two lines. With this one I need to read through example cases, etc. before receiving an answer to the question. A perfect answer would have the accepted answer as the first paragraph followed by this one. Background info is nice, but sometimes it's also refreshing to just get the answer.Meri
So that's how babby formed!Wellrounded
N
21

In general terms, the value assigned by the as part of a with statement is going to be whatever gets returned by the __enter__ method of the context manager.

Necrose answered 24/5, 2009 at 18:18 Comment(1)
Was suspecting this. Thanks for the confirmation!Meri
V
1

The with statement is there to allow for example making sure that transaction is started and stopped correctly.

In case of database connections in python, I think the natural thing to do is to create a cursor at the beginning of the with statement and then commit or rollback the transaction at the end of it.

The two blocks you gave are same from the with statement point of view. You can add the as to the first one just as well and get the cursor.

You need to check how the with support is implemented in the object you use it with.

See http://docs.python.org/whatsnew/2.5.html#pep-343-the-with-statement

Visby answered 24/5, 2009 at 11:36 Comment(0)
J
0

Let see the effect of with on decimal.localcontext which is a context manager, i.e. a class which implements the context management protocol and can be used with with/as. This context manager is used to change the current thread execution context.

Effect of with localcontext() as ctx

from decimal import localcontext
with localcontext() as ctx:
    print(type(ctx))
    print(ctx.prec) # Current calculation precision
    ctx.prec = 48 # Change calculation precision
    [...]
  • localcontext() creates an instance of ContextManager.
  • with calls ContextManager.__enter__(). This method activates a new context. The new context is a copy of the current context.__enter__ returns this Context instance.
  • as assigns the returned Context to ctx.
  • print(ctx.prec) prints the precision of the current context.
  • ctx.prec=48 changes the context precision, calculations can be performed with this new precision.
  • At the end of the width block, ContextManager.__exit__() is called by with, it restores the previous Context instance.

This outputs:

<class 'decimal.Context'>
28

Effect of with localcontext()

ctx = localcontext()
with ctx:
    print(type(ctx))
    print(ctx.prec) # Doesn't work
  • ctx = localcontext() creates an instance of ContextManager and assigns it to ctx.

  • with ctx calls ContextManager.__enter__(), which creates a copy of the current Context instance, and sets it as the current context. The new Context is returned but discarded (no as clause).

  • print(ctx.prec) tries to use attribute prec of the ContextManager instance, which doesn't exist.

This outputs:

<class 'decimal.ContextManager'>
AttributeError: 'decimal.ContextManager' object has no attribute 'prec'
Julissajulita answered 5/4 at 12:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.