views:

170

answers:

3

Is there a better way than using globals to get interesting values from a context manager?

@contextmanager
def transaction():
    global successCount
    global errorCount
    try:
        yield
    except:
        storage.store.rollback()
        errorCount += 1
    else:
        storage.store.commit()
        successCount += 1

Other possibilities:

  • singletons

    sort of globals...

  • tuple as an argument to the context manager

    makes the function more specific to a problem /less reusable

  • instance that holds the specific attributes as an argument to the context manager

    same problems as the tuple, but more legible

  • raise an exception at the end of the context manager holding the values.

    really bad idea

+6  A: 

See http://docs.python.org/reference/datamodel.html#context-managers

Create a class which holds the success and error counts, and which implements the __enter__ and __exit__ methods.

Jonas Kölker
Right. But beware the live time of the context manager. Use class attributes to store the interesting values so they can be retrieved even after the context manager goes out of scope.
Ber
+3  A: 

I still think you should be creating a class to hold you error/success counts, as I said in you last question. I'm guessing you have your own class, so just add something like this to it:

class transaction:
    __init__(self):
        self.errorCount = 0
        self.successCount = 0    
    __exit__(self, type, value, traceback):
        if type:
            storage.store.rollback()
            self.errorCount += 1
        else:
            storage.store.commit()
            self.successCount += 1

(type is None if there are no exceptions once invoking the contextmanager)

And then you probably are already using this somewhere, which will invoke the contextmanager and run your __exit__() code. Edit: As Eli commented, only create a new transaction instance when you want to reset the coutners.

t = transaction()
for q in queries:
    with t:
        t.execute(q)
jcoon
If you say "with transaction() as t:" then that will make a new transaction every time the with statement and its contents are executed. He'd really want a (possibly global) t variable so that he can just say "with t:"
Eli Courtwright
Good point, thanks.
jcoon
A: 

"tuple as an argument to the context manager

makes the function more specific to a problem /less reusable"

False.

This makes the context manager retain state.

If you don't implement anything more than this, it will be reusable.

However, you can't actually use a tuple because it's immutable. You need some mutable collection. Dictionaries and class definitions come to mind.

Consequently, the recommended implementation is

"instance that holds the specific attributes as an argument to the context manager"

A simple class definition with two attributes is all you need. However, your transaction status is stateful and you need to retain state somewhere.

class Counters(dict):
    SUCCEED= 0
    FAIL= 1
    def __init__( self ):
        self[ self.SUCCEED ]= 0
        self[ self.FAIL ]= 0 
    def increment( self, status ):
        self[status] += 1

class Transaction(object):
    def __init__( self, worker, counters ):
        self.worker= worker
        self.counters= counters
    def __enter__( self ):
        self.counters.status= None
    def process( self, *args, **kw ):
        status= self.worker.execute( *args, **kw )
        self.counters.increment( status )
    def __exit__( self ):
        pass

counts= Counters()
for q in queryList:
    with Transaction(execQuery,counts) as t:
        t.process( q )
print counts
S.Lott
Where would you increase the error and the success count? In or outside of the context manager?
Georg