Mikko Rantanen
Mikko Rantanen

Reputation: 8084

Python's 'with' statement versus 'with .. as'

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?

Upvotes: 20

Views: 13043

Answers (4)

mins
mins

Reputation: 7504

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'

Upvotes: 0

dF.
dF.

Reputation: 75785

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.

Upvotes: 42

pantsgolem
pantsgolem

Reputation: 2360

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.

Upvotes: 22

iny
iny

Reputation: 7591

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

Upvotes: 1

Related Questions